diff --git a/.clang-tidy b/.clang-tidy new file mode 100644 index 0000000000..7cb3c16679 --- /dev/null +++ b/.clang-tidy @@ -0,0 +1,13 @@ +--- +Checks: > + -clang-analyzer-security.insecureAPI.strcat, + -clang-analyzer-security.insecureAPI.strcpy, + modernize-shrink-to-fit, + modernize-use-default-member-init, + modernize-use-equals-default, + modernize-use-equals-delete, + modernize-use-override, + performance-* +HeaderFilterRegex: .* +WarningsAsErrors: '*' +... diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..f12d8c89e7 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,13 @@ +# These are supported funding model platforms + +github: [farindk] +patreon: # Replace with a single Patreon username +open_collective: # Replace with a single Open Collective username +ko_fi: # Replace with a single Ko-fi username +tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel +community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry +liberapay: # Replace with a single Liberapay username +issuehunt: # Replace with a single IssueHunt username +otechie: # Replace with a single Otechie username +lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry +custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2'] diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 91766ca8b8..17aa200b00 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,22 +12,19 @@ jobs: matrix: env: - { NAME: "nothing" } - - { NAME: "cmake", WITH_GRAPHICS: 1, WITH_X265: 1, WITH_AOM: 1, WITH_DAV1D: 1, WITH_RAV1E: 1, WITH_LIBDE265: 1, CMAKE: 1 } - - { NAME: "tarball", WITH_GRAPHICS: 1, WITH_X265: 1, WITH_AOM: 1, WITH_DAV1D: 1, WITH_RAV1E: 1, WITH_LIBDE265: 1, TARBALL: 1 } + - { NAME: "tarball", WITH_GRAPHICS: 1, WITH_X265: 1, WITH_AOM: 1, WITH_LIBDE265: 1, TARBALL: 1 } - { NAME: "graphics", WITH_GRAPHICS: 1 } - { NAME: "x265", WITH_X265: 1 } - { NAME: "x265 / graphics", WITH_GRAPHICS: 1, WITH_X265: 1 } - { NAME: "libde265 (1) / graphics", WITH_GRAPHICS: 1, WITH_LIBDE265: 1 } - - { NAME: "libde265 (2) / graphics", WITH_GRAPHICS: 1, WITH_LIBDE265: 2 } - { NAME: "libde265 (1) / x265 / graphics", WITH_GRAPHICS: 1, WITH_X265: 1, WITH_LIBDE265: 1 } - - { NAME: "libde265 (2) / x265 / graphics", WITH_GRAPHICS: 1, WITH_X265: 1, WITH_LIBDE265: 2 } - { NAME: "libde265 (1) / x265 / aom / graphics", WITH_GRAPHICS: 1, WITH_X265: 1, WITH_AOM: 1, WITH_LIBDE265: 1 } - { NAME: "libde265 (1) / x265 / dav1d / graphics", WITH_GRAPHICS: 1, WITH_X265: 1, WITH_DAV1D: 1, WITH_LIBDE265: 1 } - { NAME: "libde265 (1) / x265 / aom / rav1e / graphics", WITH_GRAPHICS: 1, WITH_X265: 1, WITH_AOM: 1, WITH_RAV1E: 1, WITH_LIBDE265: 1 } env: ${{ matrix.env }} - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - name: Install dependencies run: | diff --git a/.github/workflows/cifuzz.yml b/.github/workflows/cifuzz.yml new file mode 100644 index 0000000000..e7e11ce010 --- /dev/null +++ b/.github/workflows/cifuzz.yml @@ -0,0 +1,24 @@ +name: CIFuzz +on: [pull_request] +jobs: + Fuzzing: + runs-on: ubuntu-22.04 + steps: + - name: Build Fuzzers + id: build + uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master + with: + oss-fuzz-project-name: 'libheif' + dry-run: false + - name: Run Fuzzers + uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master + with: + oss-fuzz-project-name: 'libheif' + fuzz-seconds: 600 + dry-run: false + - name: Upload Crash + uses: actions/upload-artifact@v7 + if: failure() && steps.build.outcome == 'success' + with: + name: artifacts + path: ./out/artifacts diff --git a/.github/workflows/clang.yml b/.github/workflows/clang.yml index 8781a26a72..06f036e496 100644 --- a/.github/workflows/clang.yml +++ b/.github/workflows/clang.yml @@ -13,11 +13,10 @@ jobs: env: - { NAME: "nothing" } - { NAME: "libde265 (1) / x265 / aom / graphics", WITH_GRAPHICS: 1, WITH_X265: 1, WITH_AOM: 1, WITH_LIBDE265: 1 } - - { NAME: "libde265 (2) / x265 / aom / graphics", WITH_GRAPHICS: 1, WITH_X265: 1, WITH_AOM: 1, WITH_LIBDE265: 2 } env: ${{ matrix.env }} - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - name: Install dependencies run: | @@ -37,3 +36,32 @@ jobs: CXX: clang++ run: | ./scripts/run-ci.sh + + tidy: + env: + WITH_GRAPHICS: 1 + WITH_X265: 1 + WITH_AOM: 1 + WITH_DAV1D: 1 + WITH_LIBDE265: 1 + CLANG_TIDY: 1 + runs-on: ubuntu-22.04 + steps: + - uses: actions/checkout@v6 + + - name: Install dependencies + run: | + ./scripts/install-ci-linux.sh + sudo apt install clang-tidy-12 + + - name: Prepare CI + run: | + ./scripts/prepare-ci.sh + + - name: Run tests + run: | + ./scripts/run-ci.sh + + - name: Run clang-tidy + run: | + run-clang-tidy-12 -checks=-clang-diagnostic-tautological-compare,-clang-diagnostic-tautological-constant-out-of-range-compare -j$(nproc) diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml index 78a449cc94..13005a4ce3 100644 --- a/.github/workflows/coverity.yml +++ b/.github/workflows/coverity.yml @@ -2,26 +2,28 @@ name: coverity on: push: - branches: [ coverity ] + branches: [ master, coverity ] jobs: scan: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 env: TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} WITH_AOM: 1 + WITH_DAV1D: 1 WITH_GRAPHICS: 1 WITH_LIBDE265: 1 + WITH_RAV1E: 0 WITH_X265: 1 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - name: Cache Coverity build tool - uses: actions/cache@v2 + uses: actions/cache@v5 with: path: | coverity_tool.tar.gz - key: ${{ runner.os }} + key: coverity_tool-${{ runner.os }} - name: Download Coverity build tool run: | @@ -39,10 +41,13 @@ jobs: - name: Build with Coverity build tool run: | - export PATH=`pwd`/coverity_tool/bin:$PATH - cov-build --dir cov-int make + export PATH=$(pwd)/coverity_tool/bin:$PATH + export PKG_CONFIG_PATH="$(pwd)/libde265/dist/lib/pkgconfig/:$(pwd)/third-party/rav1e/dist/lib/pkgconfig/:$(pwd)/third-party/dav1d/dist/lib/x86_64-linux-gnu/pkgconfig/" + cmake --preset=develop . + cov-build --dir cov-int make -j$(nproc) - name: Submit build result to Coverity Scan + if: github.ref == 'refs/heads/coverity' run: | tar czvf libheif.tar.gz cov-int curl --form token=$TOKEN \ diff --git a/.github/workflows/diagram.yml b/.github/workflows/diagram.yml index 5ef516231f..934729c1ea 100644 --- a/.github/workflows/diagram.yml +++ b/.github/workflows/diagram.yml @@ -6,10 +6,10 @@ on: - main jobs: get_data: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - name: Checkout code - uses: actions/checkout@master + uses: actions/checkout@v6 - name: Update diagram uses: githubocto/repo-visualizer@main with: diff --git a/.github/workflows/emscripten.yml b/.github/workflows/emscripten.yml index ac6b73bbce..88eb1f1a33 100644 --- a/.github/workflows/emscripten.yml +++ b/.github/workflows/emscripten.yml @@ -9,10 +9,10 @@ on: jobs: emscripten: env: - EMSCRIPTEN_VERSION: 1.37.26 - runs-on: ubuntu-latest + EMSCRIPTEN_VERSION: 3.1.61 + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - name: Install emscripten run: | diff --git a/.github/workflows/fuzzer.yml b/.github/workflows/fuzzer.yml index 184b1e4189..d711e68489 100644 --- a/.github/workflows/fuzzer.yml +++ b/.github/workflows/fuzzer.yml @@ -11,12 +11,12 @@ jobs: env: WITH_AOM: 1 WITH_GRAPHICS: 1 - WITH_LIBDE265: 1 + WITH_LIBDE265: 2 WITH_X265: 1 FUZZER: 1 - runs-on: ubuntu-18.04 + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - name: Install dependencies run: | diff --git a/.github/workflows/gcc-versions.yml b/.github/workflows/gcc-versions.yml new file mode 100644 index 0000000000..ecede0b873 --- /dev/null +++ b/.github/workflows/gcc-versions.yml @@ -0,0 +1,39 @@ +name: gcc-versions + +on: + push: + pull_request: + +jobs: + build: + strategy: + matrix: + env: + - { NAME: "gcc-10", WITH_GRAPHICS: 1, WITH_X265: 1, WITH_AOM: 1, WITH_LIBDE265: 3, GCC: 10 } + - { NAME: "gcc-11", WITH_GRAPHICS: 1, WITH_X265: 1, WITH_AOM: 1, WITH_LIBDE265: 3, GCC: 11 } + - { NAME: "gcc-12", WITH_GRAPHICS: 1, WITH_X265: 1, WITH_AOM: 1, WITH_LIBDE265: 3, GCC: 12 } + - { NAME: "gcc-13", WITH_GRAPHICS: 1, WITH_X265: 1, WITH_AOM: 1, WITH_LIBDE265: 3, GCC: 13 } + - { NAME: "gcc-14", WITH_GRAPHICS: 1, WITH_X265: 1, WITH_AOM: 1, WITH_LIBDE265: 3, GCC: 14 } + env: ${{ matrix.env }} + runs-on: ubuntu-24.04 + steps: + - uses: actions/checkout@v6 + + - name: Install dependencies + run: | + ./scripts/install-ci-linux.sh + sudo apt install gcc-${GCC} g++-${GCC} + + - name: Prepare CI + env: + CC: gcc-${{matrix.env.GCC}} + CXX: g++-${{matrix.env.GCC}} + run: | + ./scripts/prepare-ci.sh + + - name: Run tests + env: + CC: gcc-${{matrix.env.GCC}} + CXX: g++-${{matrix.env.GCC}} + run: | + ./scripts/run-ci.sh diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index 31ae266bb4..b5090e626d 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -12,17 +12,16 @@ jobs: matrix: libde265-version: - "1" - - "2" env: WITH_LIBDE265: ${{ matrix.libde265-version }} WITH_X265: 1 WITH_GRAPHICS: 1 GO: 1 - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - - uses: actions/setup-go@v2 + - uses: actions/setup-go@v6 with: go-version: "1.10" diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 7861b74499..b47626ac26 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -10,9 +10,9 @@ jobs: cpplint: env: CPPLINT: 1 - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - name: Install dependencies run: | @@ -29,9 +29,9 @@ jobs: licenses: env: CHECK_LICENSES: 1 - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - name: Install dependencies run: | diff --git a/.github/workflows/mingw.yml b/.github/workflows/mingw.yml index 967d32722d..740382d708 100644 --- a/.github/workflows/mingw.yml +++ b/.github/workflows/mingw.yml @@ -14,9 +14,9 @@ jobs: - { MINGW: 32 } - { MINGW: 64 } env: ${{ matrix.env }} - runs-on: ubuntu-18.04 + runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - name: Install dependencies run: | diff --git a/.github/workflows/osx.yml b/.github/workflows/osx.yml index 7520e65091..6b7d7cb274 100644 --- a/.github/workflows/osx.yml +++ b/.github/workflows/osx.yml @@ -10,15 +10,15 @@ jobs: build: strategy: matrix: + runner: [ macos-15, macos-15-intel ] env: - { NAME: "nothing" } - - { NAME: "cmake", WITH_GRAPHICS: 1, WITH_X265: 1, WITH_AOM: 1, WITH_LIBDE265: 1, CMAKE: 1 } + - { NAME: "cmake", WITH_GRAPHICS: 1, WITH_X265: 1, WITH_AOM: 1, WITH_LIBDE265: 1 } - { NAME: "libde265 (1) / x265 / graphics", WITH_GRAPHICS: 1, WITH_X265: 1, WITH_LIBDE265: 1 } - - { NAME: "libde265 (2) / x265 / graphics", WITH_GRAPHICS: 1, WITH_X265: 1, WITH_LIBDE265: 2 } env: ${{ matrix.env }} - runs-on: macos-10.15 + runs-on: ${{ matrix.runner }} steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - name: Install dependencies run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 0b871dbe50..e1072d7456 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,9 +10,12 @@ jobs: test: env: TESTS: 1 - runs-on: ubuntu-latest + WITH_AOM: 1 + WITH_LIBDE265: 1 + WITH_X265: 1 + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - name: Install dependencies run: | diff --git a/.gitignore b/.gitignore index 79e9646b3c..2dd265018c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,40 +1,11 @@ -*.la -*.lo -*.o -*.pc -m4/lt*.m4 -libheif/*.a +# Please put your IDE or OS specific ignores into a global .gitignore file. +# see here: https://stackoverflow.com/questions/7335420/global-git-ignore -.deps -.libs -aclocal.m4 -autom4te.cache/ -compile -config.* -configure -depcomp -examples/heif-convert -examples/heif-enc -examples/heif-info -examples/heif-test -examples/heif-test-go -examples/heif-thumbnailer -examples/src -examples/test-c-api -go/go -go/src -install-sh -libtool -libheif.js -libheif.js.map -libheif.min.js -ltmain.sh -Makefile -Makefile.in -m4/libtool.m4 -missing -out.bin -libheif/*-fuzzer -libheif/heif_version.h -stamp-h1 -tests/test-race +build +_build +buildjs +CMakeUserPresets.json +third-party/SVT-AV1/ +third-party/dav1d/ +third-party/libwebp/ +third-party/rav1e/ \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index aa07dd647f..0f89ea4dbb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,41 +1,60 @@ -cmake_minimum_required (VERSION 3.13) +cmake_minimum_required (VERSION 3.16.3) # Oldest Ubuntu LTS (20.04 currently) -project(libheif LANGUAGES C CXX VERSION 1.12.0.0) +project(libheif LANGUAGES C CXX VERSION 1.21.2) + +# compatibility_version is never allowed to be decreased for any specific SONAME. +# Libtool in the libheif-1.15.1 release had set it to 17.0.0, so we have to use this for the v1.x.y versions. +# With v2.0.0, we can reset this to more closely follow the real version name, i.e. "2.0.0". +# CMake 3.17.0 added "MACHO_COMPATIBILITY_VERSION", but we are currently stuck at cmake 3.16.3. +set(MACOS_COMPATIBLE_VERSION "17.0.0") # https://cmake.org/cmake/help/v3.1/policy/CMP0054.html -cmake_policy(SET CMP0054 NEW) +cmake_policy(VERSION 3.0...3.22) include(GNUInstallDirs) # The version number. set (PACKAGE_VERSION ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}) -# Check for unistd.h - -include (${CMAKE_ROOT}/Modules/CheckIncludeFile.cmake) +# Check for header files. +include (${CMAKE_ROOT}/Modules/CheckIncludeFileCXX.cmake) -CHECK_INCLUDE_FILE(unistd.h HAVE_UNISTD_H) +CHECK_INCLUDE_FILE_CXX(bit HAVE_BIT) +if (HAVE_BIT) + add_definitions(-DHAVE_BIT) +endif() +CHECK_INCLUDE_FILE_CXX(unistd.h HAVE_UNISTD_H) if (HAVE_UNISTD_H) add_definitions(-DHAVE_UNISTD_H) endif() +if (APPLE) + option(BUILD_FRAMEWORK "Build as Apple Frameworks" OFF) +endif() if(NOT MSVC) - add_definitions(-Wall) - add_definitions(-Werror) - add_definitions(-Wsign-compare) - add_definitions(-Wconversion) - add_definitions(-Wno-sign-conversion) - add_definitions(-Wno-error=conversion) - add_definitions(-Wno-error=unused-parameter) - add_definitions(-Wno-error=deprecated-declarations) + # cmake 3.24 introduces this variable, but for backward compatibility, we set it explicitly + if (CMAKE_COMPILE_WARNING_AS_ERROR) + add_compile_options(-Werror) + endif () + + add_compile_options(-Wall) + add_compile_options(-Wsign-compare) + add_compile_options(-Wconversion) + add_compile_options(-Wno-sign-conversion) + add_compile_options(-Wno-error=conversion) + add_compile_options(-Wno-error=unused-parameter) + add_compile_options(-Wno-error=deprecated-declarations) + if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") + add_compile_options(-Wno-error=tautological-compare) + add_compile_options(-Wno-error=tautological-constant-out-of-range-compare) + endif () endif() -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) -set(CMAKE_CXX_VISIBILITY_PRESET hidden) set(CMAKE_VISIBILITY_INLINES_HIDDEN 1) set(CMAKE_POSITION_INDEPENDENT_CODE ON) @@ -48,122 +67,621 @@ option(BUILD_SHARED_LIBS "Build shared libraries" ON) include(CheckCXXCompilerFlag) CHECK_CXX_COMPILER_FLAG(-Wno-error=potentially-evaluated-expression has_potentially_evaluated_expression) if (has_potentially_evaluated_expression) - add_definitions(-Wno-error=potentially-evaluated-expression) + add_compile_options(-Wno-error=potentially-evaluated-expression) endif() LIST (APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/modules") -option(WITH_LIBDE265 "Build libde265 decoder" ON) +# TODO: when our minimum CMake reaches 3.20, replace this with "CMAKE__BYTE_ORDER" +include (TestBigEndian) +TEST_BIG_ENDIAN(IS_BIG_ENDIAN) +add_compile_definitions(IS_BIG_ENDIAN=${IS_BIG_ENDIAN}) + +option(HEIF_WITH_OMAF "Enable omnidirectional media format (OMAF) support." ON) + +# --- codec plugins + +option(ENABLE_PLUGIN_LOADING "Support loading of plugins" ON) +set(PLUGIN_DIRECTORY "${CMAKE_INSTALL_FULL_LIBDIR}/libheif" CACHE STRING "Plugin directory") +set(PLUGIN_INSTALL_DIRECTORY "" CACHE STRING "Plugin install directory (leaving it empty will use PLUGIN_DIRECTORY)") + +if (ENABLE_PLUGIN_LOADING) + set(PLUGIN_LOADING_SUPPORTED_AND_ENABLED TRUE) + + if (PLUGIN_INSTALL_DIRECTORY STREQUAL "") + set(COMPUTED_PLUGIN_INSTALL_DIRECTORY ${PLUGIN_DIRECTORY}) + else () + set(COMPUTED_PLUGIN_INSTALL_DIRECTORY ${PLUGIN_INSTALL_DIRECTORY}) + endif () + + install(DIRECTORY DESTINATION ${COMPUTED_PLUGIN_INSTALL_DIRECTORY} DIRECTORY_PERMISSIONS + OWNER_WRITE OWNER_READ OWNER_EXECUTE + GROUP_READ GROUP_EXECUTE + WORLD_READ WORLD_EXECUTE) +endif() + +macro(plugin_option optionVariableName displayName defaultEnabled defaultPlugin) + option(WITH_${optionVariableName} "Build ${displayName}" ${defaultEnabled}) + option(WITH_${optionVariableName}_PLUGIN "Build ${displayName} as a plugin" ${defaultPlugin}) +endmacro() + +macro(plugin_compilation_info optionVariableName detectionVariable displayName) + if (${detectionVariable}_FOUND AND WITH_${optionVariableName}_PLUGIN AND PLUGIN_LOADING_SUPPORTED_AND_ENABLED) + set(msg "+ separate plugin") + elseif (${detectionVariable}_FOUND) + set(msg "+ built-in") + elseif (WITH_${optionVariableName}) + set(msg "- not found") + else() + set(msg "- disabled") + endif () + + string(LENGTH "${displayName}" len) + math(EXPR fill "38 - ${len}") + string(SUBSTRING " " 0 ${fill} filler) + message("${displayName}${filler}: ${msg}") + unset(msg) +endmacro() + +# libde265 + +plugin_option(LIBDE265 "libde265 HEVC decoder" ON OFF) if (WITH_LIBDE265) - find_package(Libde265) -endif () + find_package(LIBDE265) +endif() -option(WITH_X265 "Build x265 encoder" ON) +# x265 + +plugin_option(X265 "x265 HEVC encoder" ON OFF) if (WITH_X265) find_package(X265) -endif () - -option(WITH_AOM "Build aom encoder/decoder" ON) -if (WITH_AOM) - find_package(LibAOM) endif() -option(WITH_RAV1E "Build rav1e encoder" ON) -if (WITH_RAV1E) - find_package(Rav1e) +# kvazaar + +plugin_option(KVAZAAR "kvazaar HEVC encoder" OFF OFF) +if (WITH_KVAZAAR) + find_package(kvazaar) + if ("${HAVE_KVAZAAR_ENABLE_LOGGING}") + add_definitions(-DHAVE_KVAZAAR_ENABLE_LOGGING=1) + else () + add_definitions(-DHAVE_KVAZAAR_ENABLE_LOGGING=0) + endif () + if (HAVE_KVAZAAR_VERSION_STRING) + add_definitions(-DHAVE_KVAZAAR_VERSION_STRING=1) + else () + add_definitions(-DHAVE_KVAZAAR_VERSION_STRING=0) + endif () endif () -option(WITH_DAV1D "Build dav1d decoder" ON) -if (WITH_DAV1D) - find_package(Dav1d) -endif () +# uvg266 -if (LIBDE265_FOUND) - message("HEIF decoder, libde265: found") -else () - message("HEIF decoder, libde265: not found") +plugin_option(UVG266 "uvg266 VVC encoder" OFF OFF) +if (WITH_UVG266) + find_package(UVG266) + if ("${HAVE_UVG266_ENABLE_LOGGING}") + add_definitions(-DHAVE_UVG266_ENABLE_LOGGING=1) + else () + add_definitions(-DHAVE_UVG266_ENABLE_LOGGING=0) + endif () endif () -if (X265_FOUND) - message("HEIF encoder, x265: found") -else () - message("HEIF encoder, x265: not found") +# vvdec + +plugin_option(VVDEC "vvdec VVC decoder" OFF OFF) +if (WITH_VVDEC) + # TODO: how to do configure vvdec cleanly? + find_package(Threads REQUIRED) + find_package(vvdec 3.0.0) + if (vvdec_FOUND) + set(vvdec_LIBRARIES vvdec::vvdec) + endif() endif () -if (AOM_ENCODER_FOUND) - message("AVIF encoder, aom: found") -else () - message("AVIF encoder, aom: not found") +# vvenc + +plugin_option(VVENC "vvenc VVC encoder" OFF OFF) +if (WITH_VVENC) + # TODO: how to do configure vvenc cleanly? + find_package(Threads REQUIRED) + find_package(vvenc 1.12.0) + if (vvenc_FOUND) + set(vvenc_LIBRARIES vvenc::vvenc) + endif() endif () -if (AOM_DECODER_FOUND) - message("AVIF decoder, aom: found") -else () - message("AVIF decoder, aom: not found") +# x264 + +plugin_option(X264 "x264 AVC encoder" ON OFF) +if (WITH_X264) + find_package(X264) +endif() + +# openh264 decoder + +plugin_option(OpenH264_DECODER "OpenH264 decoder" ON OFF) +# plugin_option(OpenH264_ENCODER "OpenH264 encoder" ON OFF) +if (WITH_OpenH264_ENCODER OR WITH_OpenH264_DECODER) + find_package(OpenH264) + + # When decoding/encoding is disabled, overwrite the *_FOUND variables, because they are used to ultimately decide what to build. + # TODO + if (OpenH264_FOUND AND WITH_OpenH264_DECODER) + set(OpenH264_DECODER_FOUND TRUE) + endif() +# if (OpenH264_FOUND AND WITH_OpenH264_ENCODER) +# set(OpenH264_ENCODER_FOUND TRUE) +# endif() +endif() + + + +# dav1d + +plugin_option(DAV1D "Dav1d AV1 decoder" OFF ON) +if (WITH_DAV1D) + find_package(DAV1D) +endif() + +# aom + +plugin_option(AOM_DECODER "AOM AV1 decoder" ON OFF) +plugin_option(AOM_ENCODER "AOM AV1 encoder" ON OFF) +if (WITH_AOM_ENCODER OR WITH_AOM_DECODER) + find_package(AOM) + + # When decoding/encoding is disabled, overwrite the *_FOUND variables, because they are used to ultimately decide what to build. + if (NOT WITH_AOM_DECODER) + set(AOM_DECODER_FOUND FALSE) + endif() + if (NOT WITH_AOM_ENCODER) + set(AOM_ENCODER_FOUND FALSE) + endif() +endif() + +# svt + +plugin_option(SvtEnc "SVT AV1 encoder" OFF ON) +if (WITH_SvtEnc) + find_package(SvtEnc) +endif() + +# rav1e + +plugin_option(RAV1E "Rav1e AV1 encoder" OFF ON) +if (WITH_RAV1E) + find_package(RAV1E) +endif() + +# jpeg + +plugin_option(JPEG_DECODER "JPEG decoder" OFF OFF) +plugin_option(JPEG_ENCODER "JPEG encoder" OFF OFF) +if (WITH_JPEG_ENCODER OR WITH_JPEG_DECODER) + find_package(JPEG) +endif() + +# openjpeg + +plugin_option(OpenJPEG_ENCODER "OpenJPEG J2K encoder" OFF ON) +plugin_option(OpenJPEG_DECODER "OpenJPEG J2K decoder" OFF ON) +if (WITH_OpenJPEG_ENCODER OR WITH_OpenJPEG_DECODER) + find_package(OpenJPEG 2) + + # Since the above does not work with Ubuntu 22.04 and Ubuntu 24.04, check again without version number + if (NOT OpenJPEG_FOUND) + find_package(OpenJPEG) + + if (OpenJPEG_FOUND AND NOT OPENJPEG_MAJOR_VERSION STREQUAL "2") + message("OpenJPEG found, but we need version 2.x.x") + set(OpenJPEG_FOUND FALSE) + endif() + endif() +endif() + +# ffmpeg + +plugin_option(FFMPEG_DECODER "FFMPEG HEVC decoder (HW accelerated)" OFF OFF) +if (WITH_FFMPEG_DECODER) + find_package(FFMPEG COMPONENTS avcodec avutil) endif () -if (RAV1E_FOUND) - message("AVIF encoder, rav1e: found") -else () - message("AVIF encoder, rav1e: not found") +# openjph + +plugin_option(OPENJPH_ENCODER "OpenJPH HT-J2K encoder" OFF ON) +# plugin_option(OPENJPH_DECODER "OpenJPH HT-J2K decoder" OFF ON) +if (WITH_OPENJPH_ENCODER OR WITH_OPENJPH_DECODER) + find_package(OPENJPH) +endif() + +# uncompressed + +option(WITH_UNCOMPRESSED_CODEC " Support internal ISO/IEC 23001-17 uncompressed codec (experimental) " OFF) + +# Web codecs + +option(WITH_WEBCODECS "Support HEVC decoding through browser web codecs API (experimental)" OFF) + +if (WITH_WEBCODECS) + set(WEBCODECS_FOUND TRUE) # There is no library for web-codecs. Always mark as found. +endif() + +# --- show codec compilation summary + +message("\n=== Summary of compiled codecs ===") +plugin_compilation_info(LIBDE265 LIBDE265 "libde265 HEVC decoder") +plugin_compilation_info(FFMPEG_DECODER FFMPEG_avcodec "FFMPEG HEVC decoder (HW acc)") +plugin_compilation_info(WEBCODECS WEBCODECS "WebCodecs HEVC decoder (experimental)") +plugin_compilation_info(X265 X265 "x265 HEVC encoder") +plugin_compilation_info(KVAZAAR KVAZAAR "Kvazaar HEVC encoder") +plugin_compilation_info(AOM_DECODER AOM_DECODER "AOM AV1 decoder") +plugin_compilation_info(AOM_ENCODER AOM_ENCODER "AOM AV1 encoder") +plugin_compilation_info(DAV1D DAV1D "Dav1d AV1 decoder") +plugin_compilation_info(SvtEnc SvtEnc "SVT AV1 encoder") +plugin_compilation_info(RAV1E RAV1E "Rav1e AV1 encoder") +plugin_compilation_info(JPEG_DECODER JPEG "JPEG decoder") +plugin_compilation_info(JPEG_ENCODER JPEG "JPEG encoder") +plugin_compilation_info(X264 X264 "x264 AVC encoder") +plugin_compilation_info(OpenH264_DECODER OpenH264_DECODER "OpenH264 decoder") +# plugin_compilation_info(OpenH264_ENCODER OpenH264_ENCODER "OpenH264 encoder") +plugin_compilation_info(OpenJPEG_DECODER OpenJPEG "OpenJPEG J2K decoder") +plugin_compilation_info(OpenJPEG_ENCODER OpenJPEG "OpenJPEG J2K encoder") +# plugin_compilation_info(OPENJPH_DECODER OPENJPH "OpenJPH HT-J2K decoder") +plugin_compilation_info(OPENJPH_ENCODER OPENJPH "OpenJPH HT-J2K encoder") +plugin_compilation_info(UVG266_ENCODER UVG266 "uvg266 VVC encoder") +plugin_compilation_info(VVENC vvenc "vvenc VVC encoder") +plugin_compilation_info(VVDEC vvdec "vvdec VVC decoder") + +# --- show summary which formats are supported + +macro(format_compilation_info formatName decoding_supported encoding_supported) + if (${decoding_supported}) + set(decoding "YES") + else() + set(decoding "NO ") + endif() + + if (${encoding_supported}) + set(encoding "YES") + else() + set(encoding "NO ") + endif() + + string(LENGTH "${formatName}" len) + math(EXPR fill "12 - ${len}") + string(SUBSTRING " " 0 ${fill} filler) + message("${formatName}${filler} ${decoding} ${encoding}") + unset(msg) +endmacro() + + +if (LIBDE265_FOUND OR FFMPEG_avcodec_FOUND OR WITH_WEBCODECS) + set(SUPPORTS_HEIC_DECODING TRUE) +endif() +if (X265_FOUND OR KVAZAAR_FOUND) + set(SUPPORTS_HEIC_ENCODING TRUE) +endif() +if (AOM_DECODER_FOUND OR DAV1D_FOUND) + set(SUPPORTS_AVIF_DECODING TRUE) +endif() +if (AOM_ENCODER_FOUND OR RAV1E_FOUND OR SvtEnc_FOUND) + set(SUPPORTS_AVIF_ENCODING TRUE) endif () +if (JPEG_FOUND AND WITH_JPEG_DECODER) + set(SUPPORTS_JPEG_DECODING TRUE) +endif() +if (JPEG_FOUND AND WITH_JPEG_ENCODER) + set(SUPPORTS_JPEG_ENCODING TRUE) +endif() +if (OpenJPEG_FOUND AND WITH_OpenJPEG_DECODER) + set(SUPPORTS_J2K_DECODING TRUE) + set(SUPPORTS_J2K_HT_DECODING TRUE) +endif() +if (OpenJPEG_FOUND AND WITH_OpenJPEG_ENCODER) + set(SUPPORTS_J2K_ENCODING TRUE) +endif() +if (OPENJPH_FOUND AND WITH_OPENJPH_ENCODER) + set(SUPPORTS_J2K_HT_ENCODING TRUE) +endif() +if (OPENJPH_FOUND AND WITH_OPENJPH_DECODER) + set(SUPPORTS_J2K_HT_ENCODING TRUE) +endif() +if (UVG266_FOUND OR vvenc_FOUND) + set(SUPPORTS_VVC_ENCODING TRUE) +endif() +if (vvdec_FOUND AND WITH_VVDEC) + set(SUPPORTS_VVC_DECODING TRUE) +endif() +if (OpenH264_DECODER_FOUND) + set(SUPPORTS_AVC_DECODING TRUE) +endif() +if (X264_FOUND) + set(SUPPORTS_AVC_ENCODING TRUE) +endif() -if (DAV1D_FOUND) - message("AVIF decoder, dav1d: found") -else () - message("AVIF decoder, dav1d: not found") +if (WITH_UNCOMPRESSED_CODEC) + set(SUPPORTS_UNCOMPRESSED_DECODING TRUE) + set(SUPPORTS_UNCOMPRESSED_ENCODING TRUE) +endif() + +message("\n=== Supported formats ===") +message("format decoding encoding") +format_compilation_info("AVC" SUPPORTS_AVC_DECODING SUPPORTS_AVC_ENCODING) +format_compilation_info("AVIF" SUPPORTS_AVIF_DECODING SUPPORTS_AVIF_ENCODING) +format_compilation_info("HEIC" SUPPORTS_HEIC_DECODING SUPPORTS_HEIC_ENCODING) +format_compilation_info("JPEG" SUPPORTS_JPEG_DECODING SUPPORTS_JPEG_ENCODING) +format_compilation_info("JPEG2000" SUPPORTS_J2K_DECODING SUPPORTS_J2K_ENCODING) +format_compilation_info("JPEG2000-HT" SUPPORTS_J2K_HT_DECODING SUPPORTS_J2K_HT_ENCODING) +format_compilation_info("Uncompressed" SUPPORTS_UNCOMPRESSED_DECODING SUPPORTS_UNCOMPRESSED_ENCODING) +format_compilation_info("VVC" SUPPORTS_VVC_DECODING SUPPORTS_VVC_ENCODING) + +message("") + +# --- Libsharpyuv color space transforms + +option(WITH_LIBSHARPYUV "Build libsharpyuv" ON) +option(WITH_LIBSHARPYUV_INTERNAL "Use the libsharpyuv build in the 'third-party' directory" OFF) +if (WITH_LIBSHARPYUV) + if (WITH_LIBSHARPYUV_INTERNAL) + set(LIBSHARPYUV_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/third-party/libwebp) + set(LIBSHARPYUV_LIBRARIES ${PROJECT_SOURCE_DIR}/third-party/libwebp/build/libsharpyuv.a) + set(LIBSHARPYUV_FOUND YES) + else() + find_package(libsharpyuv) + endif() +endif () +if (LIBSHARPYUV_FOUND) + message("libsharpyuv: found") +elseif (WITH_${variableName}) + message("libsharpyuv: not found") +else() + message("libsharpyuv: disabled") endif () +# --- Create libheif pkgconfig file -# Create libheif pkgconfig file set(prefix ${CMAKE_INSTALL_PREFIX}) -set(exec_prefix ${CMAKE_INSTALL_PREFIX}) -set(libdir ${CMAKE_INSTALL_FULL_LIBDIR}) -set(includedir ${CMAKE_INSTALL_FULL_INCLUDEDIR}) -if (LIBDE265_FOUND) - list(APPEND REQUIRES_PRIVATE "libde265") - set(have_libde265 yes) +set(exec_prefix "\${prefix}") +if(IS_ABSOLUTE "${CMAKE_INSTALL_LIBDIR}") + set(libdir "${CMAKE_INSTALL_LIBDIR}") else() - set(have_libde265 no) + set(libdir "\${exec_prefix}/${CMAKE_INSTALL_LIBDIR}") endif() -if (X265_FOUND) - list(APPEND REQUIRES_PRIVATE "x265") - set(have_x265 yes) +if(IS_ABSOLUTE "${CMAKE_INSTALL_INCLUDEDIR}") + set(includedir "${CMAKE_INSTALL_INCLUDEDIR}") else() - set(have_x265 no) + set(includedir "\${prefix}/${CMAKE_INSTALL_INCLUDEDIR}") endif() -if (AOM_DECODER_FOUND OR AOM_ENCODER_FOUND) +if (LIBDE265_FOUND AND NOT (PLUGIN_LOADING_SUPPORTED_AND_ENABLED AND WITH_LIBDE265_PLUGIN)) + list(APPEND REQUIRES_PRIVATE "libde265") +endif() +if (X265_FOUND AND NOT (PLUGIN_LOADING_SUPPORTED_AND_ENABLED AND WITH_X265_PLUGIN)) + list(APPEND REQUIRES_PRIVATE "x265") +endif() +if (KVAZAAR_FOUND AND NOT (PLUGIN_LOADING_SUPPORTED_AND_ENABLED AND WITH_KVAZAAR_PLUGIN)) + list(APPEND REQUIRES_PRIVATE "kvazaar") +endif() +if (OpenH264_FOUND AND (WITH_OpenH264_DECODER AND NOT (PLUGIN_LOADING_SUPPORTED_AND_ENABLED AND WITH_OpenH264_DECODER_PLUGIN))) + list(APPEND REQUIRES_PRIVATE "openh264") +endif() +if (X264_FOUND AND (WITH_X264 AND NOT (PLUGIN_LOADING_SUPPORTED_AND_ENABLED AND WITH_X264_PLUGIN))) + list(APPEND REQUIRES_PRIVATE "x264") +endif() +if ((AOM_DECODER_FOUND AND NOT (PLUGIN_LOADING_SUPPORTED_AND_ENABLED AND WITH_AOM_DECODER_PLUGIN)) + OR (AOM_ENCODER_FOUND AND NOT (PLUGIN_LOADING_SUPPORTED_AND_ENABLED AND WITH_AOM_ENCODER_PLUGIN))) list(APPEND REQUIRES_PRIVATE "aom") endif() -if (DAV1D_FOUND) +if (FFMPEG_DECODER_FOUND AND NOT (PLUGIN_LOADING_SUPPORTED_AND_ENABLED AND WITH_FFMPEG_DECODER_PLUGIN)) + list(APPEND REQUIRES_PRIVATE "ffmpeg") +endif() +if (DAV1D_FOUND AND NOT (PLUGIN_LOADING_SUPPORTED_AND_ENABLED AND WITH_DAV1D_PLUGIN)) list(APPEND REQUIRES_PRIVATE "dav1d") endif() -if (RAV1E_FOUND) +if (RAV1E_FOUND AND NOT (PLUGIN_LOADING_SUPPORTED_AND_ENABLED AND WITH_RAV1E_PLUGIN)) list(APPEND REQUIRES_PRIVATE "rav1e") endif() -if (AOM_DECODER_FOUND OR DAV1D_FOUND) - set(have_avif_decoder yes) -else() - set(have_avif_decoder no) +if (SvtEnc_FOUND AND NOT (PLUGIN_LOADING_SUPPORTED_AND_ENABLED AND WITH_SvtEnc_PLUGIN)) + list(APPEND REQUIRES_PRIVATE "SvtAv1Enc") endif() -if (AOM_ENCODER_FOUND OR RAV1E_FOUND) - set(have_avif_encoder yes) -else() - set(have_avif_encoder no) +if (JPEG_FOUND AND ((WITH_JPEG_DECODER AND NOT (PLUGIN_LOADING_SUPPORTED_AND_ENABLED AND WITH_JPEG_DECODER_PLUGIN)) OR (WITH_JPEG_ENCODER AND NOT (PLUGIN_LOADING_SUPPORTED_AND_ENABLED AND WITH_JPEG_ENCODER_PLUGIN)))) + list(APPEND REQUIRES_PRIVATE "libjpeg") +endif() +if (OpenJPEG_FOUND AND ((WITH_OpenJPEG_DECODER AND NOT (PLUGIN_LOADING_SUPPORTED_AND_ENABLED AND WITH_OpenJPEG_DECODER_PLUGIN)) OR (WITH_OpenJPEG_ENCODER AND NOT (PLUGIN_LOADING_SUPPORTED_AND_ENABLED AND WITH_OpenJPEG_ENCODER_PLUGIN)))) + list(APPEND REQUIRES_PRIVATE "libopenjp2") +endif() +if (OPENJPH_FOUND AND (WITH_OPENJPH_ENCODER AND NOT (PLUGIN_LOADING_SUPPORTED_AND_ENABLED AND WITH_OPENJPH_ENCODER_PLUGIN))) + list(APPEND REQUIRES_PRIVATE "openjph") +endif() +if (LIBSHARPYUV_FOUND) + list(APPEND REQUIRES_PRIVATE "libsharpyuv") +endif() +if (WITH_HEADER_COMPRESSION OR WITH_UNCOMPRESSED_CODEC) + find_package(ZLIB) + if (ZLIB_FOUND) + message("zlib found") + list(APPEND REQUIRES_PRIVATE "zlib") + else() + message("zlib not found") + endif() + + find_package(Brotli) + if (Brotli_FOUND) + message("Brotli found") + list(APPEND REQUIRES_PRIVATE "libbrotlidec") + else() + message("Brotli not found") + endif() +endif() +if (UVG266_FOUND AND (WITH_UVG266 AND NOT (PLUGIN_LOADING_SUPPORTED_AND_ENABLED AND WITH_UVG266_PLUGIN))) + list(APPEND REQUIRES_PRIVATE "uvg266") +endif() +if (VVDEC_FOUND AND (WITH_VVDEC AND NOT (PLUGIN_LOADING_SUPPORTED_AND_ENABLED AND WITH_VVDEC_PLUGIN))) + list(APPEND REQUIRES_PRIVATE "libvvdec") endif() +if (VVENC_FOUND AND (WITH_VVENC AND NOT (PLUGIN_LOADING_SUPPORTED_AND_ENABLED AND WITH_VVENC_PLUGIN))) + list(APPEND REQUIRES_PRIVATE "libvvenc") +endif() + list(JOIN REQUIRES_PRIVATE " " REQUIRES_PRIVATE) -set(VERSION ${PROJECT_VERSION}) + +include(CheckCXXSymbolExists) +check_cxx_symbol_exists(_LIBCPP_VERSION cstdlib HAVE_LIBCPP) +if(HAVE_LIBCPP) + set(LIBS_PRIVATE "-lc++") +else() + set(LIBS_PRIVATE "-lstdc++") +endif() + +if(HEIF_WITH_OMAF) + set(WITH_OMAF "1") +else () + set(WITH_OMAF "0") +endif() configure_file(libheif.pc.in ${CMAKE_CURRENT_BINARY_DIR}/libheif.pc @ONLY) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/libheif.pc DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) +# --- + option(WITH_EXAMPLES "Build examples" ON) +option(WITH_EXAMPLE_HEIF_THUMB "Build heif-thumbnailer tool" ON) +option(WITH_EXAMPLE_HEIF_VIEW "Build heif-view tool" ON) +option(WITH_GDK_PIXBUF "Build gdk-pixbuf plugin" ON) + +option(BUILD_DEVELOPMENT_TOOLS "Build development tools (heif-gen-bayer, etc.)" OFF) + +option(WITH_REDUCED_VISIBILITY "Reduced symbol visibility in library" ON) + +option(WITH_HEADER_COMPRESSION OFF) +option(ENABLE_MULTITHREADING_SUPPORT "Switch off for platforms without multithreading support" ON) +option(ENABLE_PARALLEL_TILE_DECODING "Will launch multiple decoders to decode tiles in parallel (requires ENABLE_MULTITHREADING_SUPPORT)" ON) + +option(ENABLE_EXPERIMENTAL_MINI_FORMAT "Enable experimental (draft) low-overhead box format (likely reduced interoperability)." OFF) + +if (WITH_REDUCED_VISIBILITY) + set(CMAKE_CXX_VISIBILITY_PRESET hidden) +else () + set(CMAKE_CXX_VISIBILITY_PRESET default) +endif () + +add_subdirectory(heifio) if(WITH_EXAMPLES) add_subdirectory (examples) endif() + +# --- API documentation + +# check if Doxygen is installed +option(BUILD_DOCUMENTATION "Build doxygen documentation" ON) +if (BUILD_DOCUMENTATION) + find_package(Doxygen) + if (DOXYGEN_FOUND) + # set input and output files + set(DOXYGEN_IN ${CMAKE_CURRENT_SOURCE_DIR}/libheif/Doxyfile.in) + set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile) + + # request to configure the file + configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY) + message("Doxygen build started") + + # note the option ALL which allows to build the docs together with the application + add_custom_target( doc_doxygen ALL + COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT} + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Generating API documentation with Doxygen" + VERBATIM ) + else (DOXYGEN_FOUND) + message("Doxygen tool needs to be installed to generate the API documentation") + endif (DOXYGEN_FOUND) +endif(BUILD_DOCUMENTATION) + +# --- Testing + +option(ENABLE_COVERAGE "" OFF) +if(ENABLE_COVERAGE) + # set compiler flags + set(CMAKE_CXX_FLAGS "-O0 -coverage") + + # find required tools + find_program(LCOV lcov REQUIRED) + find_program(GENHTML genhtml REQUIRED) + + # add coverage target + add_custom_target(coverage + # gather data + COMMAND ${LCOV} --directory . --capture --output-file coverage.info + # generate report + COMMAND ${GENHTML} --demangle-cpp -o coverage coverage.info + WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +endif() + +option(BUILD_TESTING "" ON) +set(CMAKE_CTEST_ARGUMENTS "--output-on-failure") +include(CTest) +if(BUILD_TESTING) + # TODO: fix tests on windows. + add_subdirectory (tests) +endif() + + +# --- Fuzzing + +option(WITH_FUZZERS "Build the fuzzers (and no other executables)" OFF) +set(FUZZING_C_COMPILER "clang" CACHE STRING "C compiler to use for fuzzing") +set(FUZZING_CXX_COMPILER "clang++" CACHE STRING "C++ compiler to use for fuzzing") +set(FUZZING_COMPILE_OPTIONS "-fsanitize=fuzzer,address,shift,integer -fno-sanitize-recover=shift,integer" CACHE STRING "Compiler options for fuzzing") +set(FUZZING_LINKER_OPTIONS "" CACHE STRING "Additional linking options for fuzzing") + +if (WITH_FUZZERS) + set(CMAKE_C_COMPILER ${FUZZING_C_COMPILER}) + set(CMAKE_CXX_COMPILER ${FUZZING_CXX_COMPILER}) + message("Using compiler: ${CMAKE_CXX_COMPILER}") + separate_arguments(FUZZING_COMPILE_OPTIONS UNIX_COMMAND "${FUZZING_COMPILE_OPTIONS}") + separate_arguments(FUZZING_LINKER UNIX_COMMAND "${FUZZING_LINKER_OPTIONS}") + add_compile_options(${FUZZING_COMPILE_OPTIONS}) + add_definitions(-DAVOID_FUZZER_FALSE_POSITIVE=1) + add_link_options(${FUZZING_COMPILE_OPTIONS} ${FUZZING_LINKER_OPTIONS}) + + add_subdirectory(fuzzing) +endif() + +if (CMAKE_CXX_COMPILER MATCHES "clang\\+\\+$") + add_compile_options(-Wno-tautological-constant-out-of-range-compare) +endif() + add_subdirectory (libheif) -add_subdirectory (gdk-pixbuf) + +if (WITH_GDK_PIXBUF) + add_subdirectory (gdk-pixbuf) +endif() + add_subdirectory (gnome) + + +# --- packaging (source code) + +set(CPACK_VERBATIM_VARIABLES YES) +set(CPACK_SOURCE_GENERATOR TGZ) +set(CPACK_SOURCE_PACKAGE_FILE_NAME libheif-${PACKAGE_VERSION}) +set(CPACK_SOURCE_IGNORE_FILES +/.git/ +/.github/ +/.gitignore$ +/build/ +/cmake-build.*/ +/.deps/ +/.idea/ +/.clang-tidy +~$ +/third-party/.*/ # only exclude the sub-directories, but keep the *.cmd files +/Testing/ +/logos/ +/Makefile$ +/libtool$ +/libheif.pc$ +stamp-h1$ +) +include(CPack) diff --git a/CMakePresets.json b/CMakePresets.json new file mode 100644 index 0000000000..a969c2833a --- /dev/null +++ b/CMakePresets.json @@ -0,0 +1,200 @@ +{ + "version": 3, + "cmakeMinimumRequired": { + "major": 3, + "minor": 0, + "patch": 0 + }, + "configurePresets": [ + { + "name": "develop", + "displayName": "development", + "description": "Enable all experimental features. Do not use plugins, compile everything built-in.", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "BUILD_SHARED_LIBS": "ON", + "BUILD_TESTING" : "ON", + "ENABLE_EXPERIMENTAL_FEATURES" : "ON", + "CMAKE_COMPILE_WARNING_AS_ERROR" : "OFF", + + "ENABLE_PLUGIN_LOADING" : "OFF", + "WITH_AOM_DECODER" : "ON", + "WITH_AOM_DECODER_PLUGIN" : "OFF", + "WITH_AOM_ENCODER" : "ON", + "WITH_AOM_ENCODER_PLUGIN" : "OFF", + "WITH_DAV1D" : "ON", + "WITH_DAV1D_PLUGIN" : "OFF", + "ENABLE_EXPERIMENTAL_MINI_FORMAT" : "ON", + "HEIF_WITH_OMAF" : "ON", + "WITH_LIBDE265" : "ON", + "WITH_LIBDE265_PLUGIN" : "OFF", + "WITH_RAV1E" : "ON", + "WITH_RAV1E_PLUGIN" : "OFF", + "WITH_SvtEnc" : "ON", + "WITH_SvtEnc_PLUGIN" : "OFF", + "WITH_X265" : "ON", + "WITH_X265_PLUGIN" : "OFF", + "WITH_X264" : "ON", + "WITH_X264_PLUGIN" : "OFF", + "WITH_JPEG_DECODER" : "ON", + "WITH_JPEG_DECODER_PLUGIN" : "OFF", + "WITH_JPEG_ENCODER" : "ON", + "WITH_JPEG_ENCODER_PLUGIN" : "OFF", + "WITH_UNCOMPRESSED_CODEC" : "ON", + "WITH_KVAZAAR" : "ON", + "WITH_KVAZAAR_PLUGIN" : "OFF", + "WITH_OpenJPEG_DECODER" : "ON", + "WITH_OpenJPEG_DECODER_PLUGIN" : "OFF", + "WITH_OpenJPEG_ENCODER" : "ON", + "WITH_OpenJPEG_ENCODER_PLUGIN" : "OFF", + "WITH_OPENJPH_ENCODER" : "ON", + "WITH_OPENJPH_ENCODER_PLUGIN" : "OFF", + "WITH_FFMPEG_DECODER" : "ON", + "WITH_FFMPEG_DECODER_PLUGIN" : "OFF", + "WITH_OpenH264_DECODER" : "ON", + "WITH_OpenH264_DECODER_PLUGIN" : "OFF", + "WITH_UVG266" : "ON", + "WITH_UVG266_PLUGIN" : "OFF", + "WITH_VVDEC" : "ON", + "WITH_VVDEC_PLUGIN" : "OFF", + "WITH_VVENC" : "ON", + "WITH_VVENC_PLUGIN" : "OFF", + + "WITH_REDUCED_VISIBILITY" : "OFF", + "WITH_HEADER_COMPRESSION" : "ON", + "WITH_LIBSHARPYUV" : "ON", + "WITH_GEOTIFF" : "ON", + "WITH_EXAMPLES": "ON", + "WITH_FUZZERS": "OFF", + "BUILD_DEVELOPMENT_TOOLS": "ON" + } + }, + { + "name": "release", + "displayName": "Standard release build", + "description": "Recommended parameters for a release build.", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "BUILD_SHARED_LIBS": "ON", + "BUILD_TESTING" : "OFF", + "ENABLE_EXPERIMENTAL_FEATURES" : "OFF", + "HEIF_WITH_OMAF" : "ON", + "CMAKE_COMPILE_WARNING_AS_ERROR" : "OFF", + + "ENABLE_PLUGIN_LOADING" : "ON", + "WITH_AOM_DECODER" : "ON", + "WITH_AOM_DECODER_PLUGIN" : "ON", + "WITH_AOM_ENCODER" : "ON", + "WITH_AOM_ENCODER_PLUGIN" : "ON", + "WITH_DAV1D" : "ON", + "WITH_DAV1D_PLUGIN" : "ON", + "WITH_LIBDE265" : "ON", + "WITH_LIBDE265_PLUGIN" : "ON", + "WITH_RAV1E" : "ON", + "WITH_RAV1E_PLUGIN" : "ON", + "WITH_SvtEnc" : "ON", + "WITH_SvtEnc_PLUGIN" : "ON", + "WITH_X265" : "ON", + "WITH_X265_PLUGIN" : "ON", + "WITH_JPEG_DECODER" : "ON", + "WITH_JPEG_DECODER_PLUGIN" : "ON", + "WITH_JPEG_ENCODER" : "ON", + "WITH_JPEG_ENCODER_PLUGIN" : "ON", + "WITH_UNCOMPRESSED_CODEC" : "ON", + "WITH_KVAZAAR" : "ON", + "WITH_KVAZAAR_PLUGIN" : "ON", + "WITH_OpenJPEG_DECODER" : "ON", + "WITH_OpenJPEG_DECODER_PLUGIN" : "ON", + "WITH_OpenJPEG_ENCODER" : "ON", + "WITH_OpenJPEG_ENCODER_PLUGIN" : "ON", + "WITH_OPENJPH_ENCODER" : "ON", + "WITH_FFMPEG_DECODER" : "ON", + "WITH_FFMPEG_DECODER_PLUGIN" : "ON", + "WITH_OpenH264_DECODER" : "ON", + "WITH_OpenH264_DECODER_PLUGIN" : "ON", + "WITH_UVG266" : "ON", + "WITH_UVG266_PLUGIN" : "ON", + "WITH_VVDEC" : "ON", + "WITH_VVDEC_PLUGIN" : "ON", + "WITH_VVENC" : "ON", + "WITH_VVENC_PLUGIN" : "ON", + + "WITH_REDUCED_VISIBILITY" : "ON", + "WITH_HEADER_COMPRESSION" : "ON", + "WITH_LIBSHARPYUV" : "ON", + "WITH_EXAMPLES": "ON", + "WITH_FUZZERS": "OFF" + } + }, + { + "name": "release-noplugins", + "displayName": "Release build without plugins", + "description": "Release without plugins with minimal configuration for HEIC and AVIF.", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release", + "BUILD_SHARED_LIBS": "ON", + "BUILD_TESTING" : "OFF", + "ENABLE_EXPERIMENTAL_FEATURES" : "OFF", + "HEIF_WITH_OMAF" : "ON", + "CMAKE_COMPILE_WARNING_AS_ERROR" : "OFF", + + "ENABLE_PLUGIN_LOADING" : "OFF", + "WITH_AOM_DECODER" : "ON", + "WITH_AOM_ENCODER" : "ON", + "WITH_DAV1D" : "OFF", + "WITH_LIBDE265" : "ON", + "WITH_RAV1E" : "OFF", + "WITH_SvtEnc" : "OFF", + "WITH_X265" : "ON", + "WITH_JPEG_DECODER" : "OFF", + "WITH_JPEG_ENCODER" : "OFF", + "WITH_UNCOMPRESSED_CODEC" : "OFF", + "WITH_KVAZAAR" : "OFF", + "WITH_OpenJPEG_DECODER" : "OFF", + "WITH_OpenJPEG_ENCODER" : "OFF", + "WITH_FFMPEG_DECODER" : "OFF", + "WITH_OpenH264_DECODER" : "OFF", + "WITH_UVG266" : "OFF", + "WITH_VVDEC" : "OFF", + "WITH_VVENC" : "OFF", + + "WITH_REDUCED_VISIBILITY" : "ON", + "WITH_HEADER_COMPRESSION" : "OFF", + "WITH_LIBSHARPYUV" : "ON", + "WITH_EXAMPLES": "ON", + "WITH_FUZZERS": "OFF" + } + }, + { + "name": "testing", + "displayName": "Tests", + "description": "For running the tests", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "ENABLE_PLUGIN_LOADING" : "OFF", + "BUILD_TESTING" : "ON", + "ENABLE_EXPERIMENTAL_FEATURES" : "ON", + "CMAKE_COMPILE_WARNING_AS_ERROR" : "ON", + "WITH_REDUCED_VISIBILITY" : "OFF", + "WITH_UNCOMPRESSED_CODEC" : "ON" + } + }, + { + "name": "fuzzing", + "inherits": "release", + "displayName": "Fuzzing", + "description": "For running the fuzzers", + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug", + "BUILD_SHARED_LIBS": "OFF", + "BUILD_TESTING" : "OFF", + "ENABLE_EXPERIMENTAL_FEATURES" : "ON", + "CMAKE_COMPILE_WARNING_AS_ERROR" : "ON", + "WITH_FUZZERS" : "ON", + "WITH_EXAMPLES" : "OFF", + "ENABLE_PLUGIN_LOADING" : "OFF", + "WITH_REDUCED_VISIBILITY" : "OFF" + } + } + ] +} diff --git a/COPYING b/COPYING index 5a83fd5b24..f80407e7d5 100644 --- a/COPYING +++ b/COPYING @@ -1,5 +1,5 @@ * The library `libheif` is distributed under the terms of the GNU Lesser General Public License. -* The sample applications are distributed under the terms of the MIT License. +* The sample applications and the Go and C++ wrappers are distributed under the terms of the MIT License. License texts below and in the `COPYING` files of the corresponding subfolders. diff --git a/Makefile.am b/Makefile.am deleted file mode 100644 index b7b31a0158..0000000000 --- a/Makefile.am +++ /dev/null @@ -1,27 +0,0 @@ -ACLOCAL_AMFLAGS = -I m4 - -SUBDIRS = \ - libheif \ - examples \ - extra \ - fuzzing \ - gdk-pixbuf \ - gnome \ - go \ - scripts \ - tests - -pkgconfigdir = $(libdir)/pkgconfig -pkgconfig_DATA = libheif.pc - -EXTRA_DIST = \ - .github/workflows/*.yml \ - appveyor.yml \ - autogen.sh \ - CMakeLists.txt \ - CPPLINT.cfg \ - README.md \ - pre.js \ - post.js \ - build-emscripten.sh \ - cmake/modules/*.cmake diff --git a/README.md b/README.md index b3e1933692..26607f2672 100644 --- a/README.md +++ b/README.md @@ -2,53 +2,56 @@ [![Build Status](https://github.com/strukturag/libheif/workflows/build/badge.svg)](https://github.com/strukturag/libheif/actions) [![Build Status](https://ci.appveyor.com/api/projects/status/github/strukturag/libheif?svg=true)](https://ci.appveyor.com/project/strukturag/libheif) [![Coverity Scan Build Status](https://scan.coverity.com/projects/16641/badge.svg)](https://scan.coverity.com/projects/strukturag-libheif) - -libheif is an ISO/IEC 23008-12:2017 HEIF and AVIF (AV1 Image File Format) file format decoder and encoder. - -HEIF and AVIF are new image file formats employing HEVC (h.265) or AV1 image coding, respectively, for the +libheif is an ISO/IEC 23008-12 HEIF and AVIF (AV1 Image File Format) file format decoder and encoder. +HEIC and AVIF are new image file formats employing HEVC (H.265) or AV1 image coding, respectively, for the best compression ratios currently possible. -libheif makes use of [libde265](https://github.com/strukturag/libde265) for HEIF image decoding and x265 for encoding. -For AVIF, libaom, dav1d, or rav1e are used as codecs. +On top of HEIC and AVIF, libheif also supports HEIF images coded with VVC, AVC, JPEG, JPEG-2000, and ISO/IEC 23001-17. +The ISO/IEC 23001-17 codec is built-in to libheif and allows to store lossless images and video in many different formats. +libheif makes use of various codec libraries for implementing each compression format. +For HEIC, [libde265](https://github.com/strukturag/libde265) is used by default for decoding and x265 for encoding. +For AVIF, libaom, dav1d, svt-av1, or rav1e are used as codecs. +libheif can be built with a subset of the supported codecs to keep the size and the number of dependencies low. +Alternatively, the libheif codecs can also be built as separate plugins that can be installed and loaded dynamically when used. ## Supported features -libheif has support for decoding -* tiled images -* alpha channels -* thumbnails -* reading EXIF and XMP metadata -* reading the depth channel +libheif has support for: + +* HEIC, AVIF, VVC, AVC, JPEG-in-HEIF, JPEG2000, uncompressed (ISO/IEC 23001-17:2024) codecs +* alpha channels, depth maps, thumbnails, auxiliary images * multiple images in a file -* image transformations (crop, mirror, rotate) -* overlay images -* plugin interface to add alternative codecs for additional formats (AVC, JPEG) -* decoding of files while downloading (e.g. extract image size before file has been completely downloaded) -* reading color profiles -* heix images (10 and 12 bit, chroma 4:2:2) - -The encoder supports: -* lossy compression with adjustable quality -* lossless compression -* alpha channels -* thumbnails -* save multiple images to a file -* save EXIF and XMP metadata -* writing color profiles -* 10 and 12 bit images -* monochrome images - -## API +* HEIF image sequences and MP4 video, including alpha channels +* tiled images with decoding individual tiles and encoding tiled images by adding tiles one after another +* HDR images, correct color transform according to embedded color profiles +* image transformations (crop, mirror, rotate), overlay images +* plugin interface to add alternative codecs +* reading EXIF and XMP metadata +* region annotations and mask images +* streaming of images and video by requesting data from the network through a data-reader interface + +Supported codecs: +| Format | Decoders | Encoders | +|:-------------|:-------------------:|:----------------------------:| +| HEIC | libde265, ffmpeg | x265, kvazaar | +| AVIF | libaom, dav1d | libaom, rav1e, svt-av1 | +| VVC | vvdec | vvenc, uvg266 | +| AVC | openh264, ffmpeg | x264 | +| JPEG | libjpeg(-turbo) | libjpeg(-turbo) | +| JPEG2000 | OpenJPEG | OpenJPEG | +| HTJ2K | OpenJPEG | OpenJPH | +| uncompressed | built-in | built-in | + +## Programming API The library has a C API for easy integration and wide language support. -Note that the API is still work in progress and may still change. -The decoder automatically supports both HEIF and AVIF through the same API. No changes are required to existing code to support AVIF. +The decoder automatically supports both HEIF and AVIF (and the other compression formats) through the same API. The same decoding code can be used to decode any of them. The encoder can be switched between HEIF and AVIF simply by setting `heif_compression_HEVC` or `heif_compression_AV1` -to `heif_context_get_encoder_for_format()`. +to `heif_context_get_encoder_for_format()`, or using any of the other compression formats. -Loading the primary image in an HEIF file is as easy as this: +### Loading the primary image from a HEIF file ```C heif_context* ctx = heif_context_alloc(); @@ -64,9 +67,16 @@ heif_decode_image(handle, &img, heif_colorspace_RGB, heif_chroma_interleaved_RGB int stride; const uint8_t* data = heif_image_get_plane_readonly(img, heif_channel_interleaved, &stride); + +// ... process data as needed ... + +// clean up resources +heif_image_release(img); +heif_image_handle_release(handle); +heif_context_free(ctx); ``` -Writing an HEIF file can be done like this: +### Writing a HEIF file ```C heif_context* ctx = heif_context_alloc(); @@ -84,126 +94,285 @@ heif_context_encode_image(ctx, image, encoder, nullptr, nullptr); heif_encoder_release(encoder); -heif_context_write_to_file(context, "output.heic"); +heif_context_write_to_file(ctx, "output.heic"); + +heif_context_free(ctx); ``` -See the header file `heif.h` for the complete C API. +### Get the EXIF data from a HEIF file + +```C +heif_item_id exif_id; + +int n = heif_image_handle_get_list_of_metadata_block_IDs(image_handle, "Exif", &exif_id, 1); +if (n==1) { + size_t exifSize = heif_image_handle_get_metadata_size(image_handle, exif_id); + uint8_t* exifData = malloc(exifSize); + struct heif_error error = heif_image_handle_get_metadata(image_handle, exif_id, exifData); +} +``` + +### Image sequences, MP4 video + +See the [image sequences API documentation](https://github.com/strukturag/libheif/wiki/Reading-and-Writing-Sequences). + +Since HEIF image sequences are very similar to MP4 video, libheif can also read and write MP4 video (without audio) +with all supported codecs. + +### High-resolution tiled images + +For very large resolution images, it is not always feasible to process the whole image. +In this case, `libheif` can process the image tile by tile. +See the [image tiling API documentation](https://github.com/strukturag/libheif/wiki/Reading-and-Writing-Tiled-Images). + +### More documenation + +See the header files for the complete C API. There is also a C++ API which is a header-only wrapper to the C API. Hence, you can use the C++ API and still be binary compatible. Code using the C++ API is much less verbose than using the C API directly. -There is also an experimental Go API, but this is not stable yet. - ## Compiling -This library uses either a standard autoconf/automake build system or CMake. +This library uses the CMake build system (the earlier autotools build files have been removed in v1.16.0). -When using autoconf, run `./autogen.sh` to build the configuration scripts, -then call `./configure` and `make`. +For a minimal configuration, we recommend to use the codecs libde265 and x265 for HEIC and AOM for AVIF. Make sure that you compile and install [libde265](https://github.com/strukturag/libde265) first, so that the configuration script will find this. -Preferably, download the `frame-parallel` branch of libde265, as this uses a -more recent API than the version in the `master` branch. -Also install x265 and its development files if you want to use HEIF encoding. - -For AVIF support, make sure that libaom is installed. +Also install x265 and its development files if you want to use HEIF encoding, but note that x265 is GPL. +An alternative to x265 is kvazaar (BSD). + +The basic build steps are as follows (--preset argument needs CMake >= 3.21): + +````sh +mkdir build +cd build +cmake --preset=release .. +make +```` + +There are CMake presets to cover the most frequent use cases. + +* `release`: the preferred preset which compiles all codecs as separate plugins. + If you do not want to distribute some of these plugins (e.g. HEIC), you can omit packaging these. +* `release-noplugins`: this is a smaller, self-contained build of libheif without using the plugin system. + A single library is built with support for HEIC and AVIF. +* `testing`: for building and executing the unit tests. Also the internal library symbols are exposed. Do not use for distribution. +* `fuzzing`: all codecs like in release build, but configured into a self-contained library with enabled fuzzers. The library should not distributed. + +You can optionally adapt these standard configurations to your needs. +This can be done, for example, by calling `ccmake .` from within the `build` directory. + +### CMake configuration variables + +Libheif supports many different codecs. In order to reduce the number of dependencies and the library size, +you can choose which of these codecs to include. Each codec can be compiled either as built-in to the library +with a hard dependency, or as a separate plugin file that is loaded dynamically. + +For each codec, there are two configuration variables: + +* `WITH_{codec}`: enables the codec +* `WITH_{codec}_PLUGIN`: when enabled, the codec is compiled as a separate plugin. + +In order to use dynamic plugins, also make sure that `ENABLE_PLUGIN_LOADING` is enabled. +The placeholder `{codec}` can have these values: `LIBDE265`, `X265`, `AOM_DECODER`, `AOM_ENCODER`, `SvtEnc`, `DAV1D`, `OpenH264`, `X264`, `FFMPEG_DECODER`, `JPEG_DECODER`, `JPEG_ENCODER`, `KVAZAAR`, `OpenJPEG_DECODER`, `OpenJPEG_ENCODER`, `OPENJPH_ENCODER`, `VVDEC`, `VVENC`, `UVG266`, `WEBCODECS`. + +Further options are: + +* `WITH_UNCOMPRESSED_CODEC`: enable support for uncompressed images according to ISO/IEC 23001-17:2024. This is *experimental* + and not available as a dynamic plugin. When enabled, it adds a dependency to `zlib`, and optionally will use `brotli`. +* `WITH_HEADER_COMPRESSION`: enables support for compressed metadata. When enabled, it adds a dependency to `zlib`. + Note that header compression is not widely supported yet. +* `WITH_LIBSHARPYUV`: enables high-quality YCbCr/RGB color space conversion algorithms (requires `libsharpyuv`, + e.g. from the `third-party` directory). +* `ENABLE_EXPERIMENTAL_FEATURES`: enables functions that are currently in development and for which the API is not stable yet. + When this is enabled, a header `heif_experimental.h` will be installed that contains this unstable API. + Distributions that rely on a stable API should not enable this. +* `ENABLE_MULTITHREADING_SUPPORT`: can be used to disable any multithreading support, e.g. for embedded platforms. +* `ENABLE_PARALLEL_TILE_DECODING`: when enabled, libheif will decode tiled images in parallel to speed up compilation. +* `PLUGIN_DIRECTORY`: the directory where libheif will search for dynamic plugins when the environment + variable `LIBHEIF_PLUGIN_PATH` is not set. +* `WITH_REDUCED_VISIBILITY`: only export those symbols into the library that are public API. + Has to be turned off for running some tests. ### macOS 1. Install dependencies with Homebrew - ``` - brew install automake make pkg-config x265 libde265 libjpeg + ```sh + brew install cmake make pkg-config x265 libde265 libjpeg libtool ``` +2. Configure and build project (--preset argument needs CMake >= 3.21): -1. Configure and build project - - ``` - ./autogen.sh + ```sh + mkdir build + cd build + cmake --preset=release .. ./configure make ``` ### Windows -Libheif is included in [Vcpkg](https://github.com/Microsoft/vcpkg/). +You can build and install libheif using the [vcpkg](https://github.com/Microsoft/vcpkg/) dependency manager: + +```sh +git clone https://github.com/Microsoft/vcpkg.git +cd vcpkg +./bootstrap-vcpkg.bat +./vcpkg integrate install +./vcpkg install libheif +``` + +The libheif port in vcpkg is kept up to date by Microsoft team members and community contributors. If the version is out of date, please [create an issue or pull request](https://github.com/Microsoft/vcpkg) on the vcpkg repository. + +### Adding libaom encoder/decoder for AVIF +* Run the `aom.cmd` script in the `third-party` directory to download libaom and + compile it. + +When running `cmake` or `configure`, make sure that the environment variable +`PKG_CONFIG_PATH` includes the absolute path to `third-party/aom/dist/lib/pkgconfig`. ### Adding rav1e encoder for AVIF * Install `cargo`. * Install `cargo-c` by executing -``` + +```sh cargo install --force cargo-c ``` -* Run the `rav1e.cmd` script in directory `third-party` to download rav1e and compile it. -When running `cmake` or `configure`, make sure that the environment variable -`PKG_CONFIG_PATH` includes the absolute path to `third-party/rav1e/dist/lib/pkgconfig`. +* Run the `rav1e.cmd` script in the `third-party` directory to download rav1e + and compile it. +When running `cmake`, make sure that the environment variable +`PKG_CONFIG_PATH` includes the absolute path to `third-party/rav1e/dist/lib/pkgconfig`. ### Adding dav1d decoder for AVIF * Install [`meson`](https://mesonbuild.com/). -* Run the `dav1d.cmd` script in directory `third-party` to download dav1d and compile it. +* Run the `dav1d.cmd` script in the `third-party` directory to download dav1d + and compile it. -When running `cmake` or `configure`, make sure that the environment variable +When running `cmake`, make sure that the environment variable `PKG_CONFIG_PATH` includes the absolute path to `third-party/dav1d/dist/lib/x86_64-linux-gnu/pkgconfig`. +### Adding SVT-AV1 encoder for AVIF + +You can either use the SVT-AV1 encoder libraries installed in the system or use a self-compiled current version. +If you want to compile SVT-AV1 yourself, + +* Run the `svt.cmd` script in the `third-party` directory to download SVT-AV1 + and compile it. + +You have to enable SVT-AV1 with CMake. + +When running `cmake`, make sure that the environment variable +`PKG_CONFIG_PATH` includes the absolute path to `third-party/SVT-AV1/Build/linux/install/lib/pkgconfig`. +You may have to replace `linux` in this path with your system's identifier. + +## Codec plugins + +Starting with v1.14.0, each codec backend can be compiled statically into libheif or as a dynamically loaded plugin. +You can choose this individually for each codec backend in the CMake settings using `WITH_{codec}_PLUGIN` options. +Compiling a codec backend as dynamic plugin will generate a shared library that is installed in the system together with libheif. +The advantage is that only the required plugins have to be installed and libheif has fewer dependencies. + +The plugins are loaded from the colon-separated (semicolon-separated on Windows) list of directories stored in the environment variable `LIBHEIF_PLUGIN_PATH`. +If this variable is empty, they are loaded from a directory specified in the CMake configuration. +You can also add plugin directories programmatically. + +### Codec specific notes + +* The FFMPEG decoding plugin can make use of h265 hardware decoders. However, it currently (v1.17.0, ffmpeg v4.4.2) does not work + correctly with all streams. Thus, libheif still prefers the libde265 decoder if it is available. + +* The "webcodecs" HEVC decoder can only be used in emscripten builds since it uses the web-browser's API. For the same reason, it is not available as a plugin. + +## Usage + +libheif comes with a set of command line tools: +* `heif-dec` for decoding HEIF images to JPEG or PNG. It can also decode image sequences or MP4 video. +* `heif-enc` for encoding JPEG, PNG, TIFF, or Y4M images to HEIF images, image sequences or MP4 video. +* `heif-info` for getting some overview information about the HEIF file or (using the `-d` option) to dump the full box structure of the file. +* `heif-view` for displaying HEIF image sequences + +### `heif-enc` command line tool + +You can find more documentation for the `heif-enc` tool on the [wiki page](https://github.com/strukturag/libheif/wiki/heif%E2%80%90enc-Command-Line-Tool). + +### Security limits + +Libheif defines some security limits that prevent that very large images exceed the available memory or malicious input files can be used for a denial-of-service attack. +When you are programming against the libheif API and you need to process very large images, you can set the `heif_security_limits` individually. +When using `heif-dec`, there is the option to switch off security limits with `--disable-limits`. +In case a third-party software is using libheif, but does not give you a way to switch off the limits, you can set an environment variable `LIBHEIF_SECURITY_LIMITS=off` to switch it off globally. +Clearly, only do this if you know what you are doing and you are sure not to process malicious files. + +## Encoder benchmark + +A current benchmark of the AVIF encoders (as of 14 Oct 2022) can be found on the Wiki page +[AVIF encoding benchmark](https://github.com/strukturag/libheif/wiki/AVIF-Encoder-Benchmark). ## Language bindings * .NET Platform (C#, F#, and other languages): [libheif-sharp](https://github.com/0xC0000054/libheif-sharp) * C++: part of libheif -* Go: part of libheif +* Go: [libheif-go](https://github.com/strukturag/libheif-go), the wrapper distributed with libheif is deprecated * JavaScript: by compilation with emscripten (see below) * NodeJS module: [libheif-js](https://www.npmjs.com/package/libheif-js) -* Python: [pyheif](https://pypi.org/project/pyheif/) +* Python: [pyheif](https://pypi.org/project/pyheif/), [pillow_heif](https://pypi.org/project/pillow-heif/) * Rust: [libheif-sys](https://github.com/Cykooz/libheif-sys) * Swift: [libheif-Xcode](https://swiftpackageregistry.com/SDWebImage/libheif-Xcode) +* JavaFX: [LibHeifFx](https://github.com/lanthale/LibHeifFX) Languages that can directly interface with C libraries (e.g., Swift, C#) should work out of the box. - -## Compiling to JavaScript +## Compiling to JavaScript / WASM libheif can also be compiled to JavaScript using [emscripten](http://kripken.github.io/emscripten-site/). -See the `build-emscripten.sh` for further information. - +It can be built like this (in the libheif directory): +```` +mkdir buildjs +cd buildjs +USE_WASM=0 ../build-emscripten.sh .. +```` +Set `USE_WASM=1` to build with WASM output. +See the `build-emscripten.sh` script for further options. ## Online demo Check out this [online demo](https://strukturag.github.io/libheif/). This is `libheif` running in JavaScript in your browser. - ## Example programs Some example programs are provided in the `examples` directory. -The program `heif-convert` converts all images stored in an HEIF/AVIF file to JPEG or PNG. +The program `heif-dec` converts all images stored in an HEIF/AVIF file to JPEG or PNG. `heif-enc` lets you convert JPEG files to HEIF/AVIF. The program `heif-info` is a simple, minimal decoder that dumps the file structure to the console. For example convert `example.heic` to JPEGs and one of the JPEGs back to HEIF: -``` +```sh cd examples/ -./heif-convert example.heic example.jpeg +./heif-dec example.heic example.jpeg ./heif-enc example-1.jpeg -o example.heif ``` In order to convert `example-1.jpeg` to AVIF use: -``` + +```sh ./heif-enc example-1.jpeg -A -o example.avif ``` There is also a GIMP plugin using libheif [here](https://github.com/strukturag/heif-gimp-plugin). - ## HEIF/AVIF thumbnails for the Gnome desktop The program `heif-thumbnailer` can be used as an HEIF/AVIF thumbnailer for the Gnome desktop. @@ -211,31 +380,45 @@ The matching Gnome configuration files are in the `gnome` directory. Place the files `heif.xml` and `avif.xml` into `/usr/share/mime/packages` and `heif.thumbnailer` into `/usr/share/thumbnailers`. You may have to run `update-mime-database /usr/share/mime` to update the list of known MIME types. - ## gdk-pixbuf loader libheif also includes a gdk-pixbuf loader for HEIF/AVIF images. 'make install' will copy the plugin into the system directories. However, you will still have to run `gdk-pixbuf-query-loaders --update-cache` to update the gdk-pixbuf loader database. - ## Software using libheif -* GIMP -* Krita -* ImageMagick -* digiKam 7.0.0 -* libvips +* [GIMP](https://www.gimp.org/) +* [Krita](https://krita.org) +* [ImageMagick](https://imagemagick.org/) +* [GraphicsMagick](http://www.graphicsmagick.org/) +* [darktable](https://www.darktable.org) +* [digiKam 7.0.0](https://www.digikam.org/) +* [libvips](https://github.com/libvips/libvips) +* [kImageFormats](https://api.kde.org/frameworks/kimageformats/html/index.html) +* [libGD](https://libgd.github.io/) * [Kodi HEIF image decoder plugin](https://kodi.wiki/view/Add-on:HEIF_image_decoder) * [bimg](https://github.com/h2non/bimg) * [GDAL](https://gdal.org/drivers/raster/heif.html) * [OpenImageIO](https://sites.google.com/site/openimageio/) +* [XnView](https://www.xnview.com) + +## Packaging status + +[![libheif packaging status](https://repology.org/badge/vertical-allrepos/libheif.svg?exclude_unsupported=1&columns=3&exclude_sources=modules,site&header=libheif%20packaging%20status)](https://repology.org/project/libheif/versions) + +## Sponsors -## Source code visualization +Since I work as an independent developer, I need your support to be able to allocate time for libheif. +You can [sponsor](https://github.com/sponsors/farindk) the development using the link in the right hand column. -![Visualization of this repo](./diagram.svg) +A big thank you goes to these major sponsors for supporting the development of libheif: -[Explore source](https://octo-repo-visualization.vercel.app/?repo=strukturag%2Flibheif) +* AOMedia +* OGC (Open Geospatial Consortium) +* Pinterest +* Shopify shopify-logo +* StrukturAG ## License @@ -244,5 +427,6 @@ The sample applications are distributed under the terms of the MIT License. See COPYING for more details. -Copyright (c) 2017-2020 Struktur AG +Copyright (c) 2017-2020 Struktur AG
+Copyright (c) 2017-2026 Dirk Farin
Contact: Dirk Farin diff --git a/appveyor.yml b/appveyor.yml index 446c7bd544..a7d1581cee 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,22 +1,40 @@ # stats available at # https://ci.appveyor.com/project/strukturag/libheif -image: Visual Studio 2019 +image: Visual Studio 2022 configuration: Release cache: c:\tools\vcpkg\installed\ -matrix: - allow_failures: - - arch: arm64 # libde265 currently doesn't support arm64 on vcpkg - environment: matrix: - arch: x64 + triplet: x64-windows + - arch: Win32 + triplet: x86-windows - arch: arm64 + triplet: arm64-windows install: - - vcpkg install libde265:%arch%-windows - - vcpkg install x265:%arch%-windows - - vcpkg install dav1d:%arch%-windows + # Build only Release vcpkg libraries (skip Debug) to halve install time + - ps: | + @('x64-windows', 'x86-windows', 'arm64-windows') | ForEach-Object { + $tripletDir = "C:\tools\vcpkg\triplets" + $content = (Get-Content "$tripletDir\$_.cmake" -Raw) + "`nset(VCPKG_BUILD_TYPE release)`n" + Set-Content "$tripletDir\$_.cmake" $content + } + # --- Phase 1: remove ffmpeg, upgrade everything else --- + #- vcpkg remove ffmpeg:%triplet% --recurse 2>NUL & ver>NUL + #- vcpkg upgrade --no-dry-run + # --- Phase 2: uncomment the two lines below to compile ffmpeg --- + - vcpkg upgrade --no-dry-run + - vcpkg install ffmpeg[avcodec]:%triplet% + - vcpkg install aom:%triplet% + - ps: If (${env:arch} -ne "Win32") { vcpkg install dav1d:${env:triplet} } + - vcpkg install libde265:%triplet% + - vcpkg install libjpeg-turbo:%triplet% + - vcpkg install libpng:%triplet% + - vcpkg install tiff:%triplet% + - vcpkg install x265:%triplet% + - vcpkg install zlib:%triplet% - cd c:\tools\vcpkg - vcpkg integrate install - cd %APPVEYOR_BUILD_FOLDER% @@ -24,9 +42,12 @@ install: before_build: - mkdir build - cd build - - cmake .. -A %arch% -DCMAKE_TOOLCHAIN_FILE=c:/tools/vcpkg/scripts/buildsystems/vcpkg.cmake + - cmake .. -A %arch% -DCMAKE_TOOLCHAIN_FILE=c:/tools/vcpkg/scripts/buildsystems/vcpkg.cmake -DWITH_DAV1D=ON -DWITH_AOM_DECODER=ON -DWITH_AOM_ENCODER=ON -DWITH_JPEG_DECODER=ON -DWITH_JPEG_ENCODER=ON -DWITH_UNCOMPRESSED_CODEC=ON -DWITH_HEADER_COMPRESSION=ON -DWITH_FFMPEG_DECODER=ON + - dir build: + project: build\libheif.sln + parallel: true verbosity: normal artifacts: diff --git a/autogen.sh b/autogen.sh deleted file mode 100755 index 2a5d449030..0000000000 --- a/autogen.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash -set -eu -# -# HEIF codec. -# Copyright (c) 2017 struktur AG, Joachim Bauch -# -# This file is part of libheif. -# -# libheif is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 3 of the License, or -# (at your option) any later version. -# -# libheif is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with libheif. If not, see . -# -ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -if [ -d "$ROOT/.git/hooks" ]; then - echo "Installing pre-commit hook ..." - ln -sf "$ROOT/scripts/pre-commit.hook" "$ROOT/.git/hooks/pre-commit" -fi - -if [ -x "`which autoreconf 2>/dev/null`" ] ; then - exec autoreconf -ivf -fi - -LIBTOOLIZE=libtoolize -SYSNAME=`uname` -if [ "x$SYSNAME" = "xDarwin" ] ; then - LIBTOOLIZE=glibtoolize -fi -aclocal -I m4 && \ - autoheader && \ - $LIBTOOLIZE && \ - autoconf && \ - automake --add-missing --force-missing --copy diff --git a/build-emscripten.sh b/build-emscripten.sh index 663e09ed2e..7fe4749a8a 100755 --- a/build-emscripten.sh +++ b/build-emscripten.sh @@ -2,60 +2,201 @@ set -e DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -CORES=$(nproc --all) +if [[ $# -ne 1 ]] ; then + echo "Usage: $0 SRCDIR" + echo + echo "It is recommended to build in a separate directory." + echo "Then specify this directory as an argument to this script." + echo "Example:" + echo " mkdir buildjs" + echo " cd buildjs" + echo " USE_WASM=0 ../build-emscripten.sh .." + echo + echo "This should generate a libheif.js and (optionally, without the USE_WASM=0) a libheif.wasm" + exit 5 +fi + +SRCDIR=$1 + +CORES="${CORES:-`nproc --all`}" + +ENABLE_LIBDE265="${ENABLE_LIBDE265:-1}" +LIBDE265_VERSION="${LIBDE265_VERSION:-1.0.15}" + +ENABLE_AOM="${ENABLE_AOM:-0}" +AOM_VERSION="${AOM_VERSION:-3.6.1}" + +# Webcodecs is not on by default b/c asyncify increases the binary size considerably +ENABLE_WEBCODECS="${ENABLE_WEBCODECS:-0}" + +ENABLE_UNCOMPRESSED="${ENABLE_UNCOMPRESSED:-0}" + +# J2K still defunct. OpenJPEG compiles, but library is not picked up by libheif cmake. +ENABLE_OPENJPEG="${ENABLE_OPENJPEG:-0}" +OPENJPEG_VERSION="${OPENJPEG_VERSION:-2.5.4}" + +STANDALONE="${STANDALONE:-0}" +DEBUG="${DEBUG:-0}" +USE_ES6="${USE_ES6:-0}" +USE_WASM="${USE_WASM:-1}" +USE_TYPESCRIPT="${USE_TYPESCRIPT:-1}" +USE_UNSAFE_EVAL="${USE_UNSAFE_EVAL:-1}" + echo "Build using ${CORES} CPU cores" -LIBDE265_VERSION=1.0.2 -[ -s "libde265-${LIBDE265_VERSION}.tar.gz" ] || curl \ - -L \ - -o libde265-${LIBDE265_VERSION}.tar.gz \ - https://github.com/strukturag/libde265/releases/download/v${LIBDE265_VERSION}/libde265-${LIBDE265_VERSION}.tar.gz -if [ ! -s "libde265-${LIBDE265_VERSION}/libde265/.libs/libde265.so" ]; then - tar xf libde265-${LIBDE265_VERSION}.tar.gz - cd libde265-${LIBDE265_VERSION} - [ -x configure ] || ./autogen.sh - emconfigure ./configure --disable-sse --disable-dec265 --disable-sherlock265 - emmake make -j${CORES} - cd .. -fi - -CONFIGURE_ARGS="--disable-multithreading --disable-go" - -emconfigure ./configure $CONFIGURE_ARGS \ - PKG_CONFIG_PATH="${DIR}/libde265-${LIBDE265_VERSION}" \ - libde265_CFLAGS="-I${DIR}/libde265-${LIBDE265_VERSION}" \ - libde265_LIBS="-L${DIR}/libde265-${LIBDE265_VERSION}/libde265/.libs" -if [ ! -e "Makefile" ]; then - # Most likely the first run of "emscripten" which will generate the - # config file and terminate. Run "emconfigure" again. - emconfigure ./configure $CONFIGURE_ARGS \ - PKG_CONFIG_PATH="${DIR}/libde265-${LIBDE265_VERSION}" \ - libde265_CFLAGS="-I${DIR}/libde265-${LIBDE265_VERSION}" \ - libde265_LIBS="-L${DIR}/libde265-${LIBDE265_VERSION}/libde265/.libs" -fi -emmake make -j${CORES} - -export TOTAL_MEMORY=16777216 +LIBRARY_LINKER_FLAGS="" +LIBRARY_INCLUDE_FLAGS="" + +CONFIGURE_ARGS_LIBDE265="" +if [ "$ENABLE_LIBDE265" = "1" ]; then + [ -s "libde265-${LIBDE265_VERSION}.tar.gz" ] || curl \ + -L \ + -o libde265-${LIBDE265_VERSION}.tar.gz \ + https://github.com/strukturag/libde265/releases/download/v${LIBDE265_VERSION}/libde265-${LIBDE265_VERSION}.tar.gz + if [ ! -s "libde265-${LIBDE265_VERSION}/libde265/.libs/libde265.a" ]; then + tar xf libde265-${LIBDE265_VERSION}.tar.gz + cd libde265-${LIBDE265_VERSION} + [ -x configure ] || ./autogen.sh + CXXFLAGS=-O3 emconfigure ./configure --enable-static --disable-shared --disable-sse --disable-dec265 --disable-sherlock265 + emmake make -j${CORES} + cd .. + fi + LIBDE265_DIR="$(pwd)/libde265-${LIBDE265_VERSION}" + CONFIGURE_ARGS_LIBDE265="-DLIBDE265_INCLUDE_DIR=${LIBDE265_DIR} -DLIBDE265_LIBRARY=-L${LIBDE265_DIR}/libde265/.libs" + LIBRARY_LINKER_FLAGS="$LIBRARY_LINKER_FLAGS -L${LIBDE265_DIR}/libde265/.libs -lde265" +fi + +CONFIGURE_ARGS_AOM="" +if [ "$ENABLE_AOM" = "1" ]; then + [ -s "aom-${AOM_VERSION}.tar.gz" ] || curl \ + -L \ + -o aom-${AOM_VERSION}.tar.gz \ + "https://aomedia.googlesource.com/aom/+archive/v${AOM_VERSION}.tar.gz" + if [ ! -s "aom-${AOM_VERSION}/libaom.a" ]; then + mkdir -p aom-${AOM_VERSION}/aom-source + tar xf aom-${AOM_VERSION}.tar.gz -C aom-${AOM_VERSION}/aom-source + cd aom-${AOM_VERSION} + + emcmake cmake aom-source \ + -DENABLE_CCACHE=1 \ + -DAOM_TARGET_CPU=generic \ + -DENABLE_DOCS=0 \ + -DENABLE_TESTS=0 \ + -DENABLE_EXAMPLES=0 \ + -DENABLE_TESTDATA=0 \ + -DENABLE_TOOLS=0 \ + -DCONFIG_MULTITHREAD=0 \ + -DCONFIG_RUNTIME_CPU_DETECT=0 \ + -DBUILD_SHARED_LIBS=0 \ + -DCMAKE_BUILD_TYPE=Release + + emmake make -j${CORES} + + cd .. + fi + + AOM_DIR="$(pwd)/aom-${AOM_VERSION}" + CONFIGURE_ARGS_AOM="-DAOM_INCLUDE_DIR=${AOM_DIR}/aom-source -DAOM_LIBRARY=-L${AOM_DIR}" + LIBRARY_LINKER_FLAGS="$LIBRARY_LINKER_FLAGS -L${AOM_DIR} -laom" +fi + +CONFIGURE_ARGS_OPENJPEG="" +if [ "$ENABLE_OPENJPEG" = "1" ]; then + [ -s "openjpeg-${OPENJPEG__VERSION}.tar.gz" ] || curl \ + -L \ + -o openjpeg-${OPENJPEG_VERSION}.tar.gz \ + "https://github.com/uclouvain/openjpeg/archive/refs/tags/v${OPENJPEG_VERSION}.tar.gz" + if [ ! -s "openjpeg-${OPENJPEG_VERSION}/bin/libopenjp2.a" ]; then + mkdir -p openjpeg-${OPENJPEG_VERSION}/openjpeg-source + tar xf openjpeg-${OPENJPEG_VERSION}.tar.gz -C openjpeg-${OPENJPEG_VERSION}/openjpeg-source + cd openjpeg-${OPENJPEG_VERSION} + emcmake cmake openjpeg-source/openjpeg-${OPENJPEG_VERSION} \ + -DBUILD_SHARED_LIBS=0 \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_INSTALL_PREFIX=openjpeg-install + + emmake make -j${CORES} + emmake make install -j${CORES} + + cd .. + fi + + J2K_DIR="$(pwd)/openjpeg-${OPENJPEG_VERSION}" + CONFIGURE_ARGS_OPENJPEG="-DWITH_OpenJPEG_DECODER=ON -DCMAKE_PREFIX_PATH=${J2K_DIR}/openjpeg-install -DCMAKE_FIND_ROOT_PATH_MODE_PACKAGE=BOTH" + LIBRARY_LINKER_FLAGS="$LIBRARY_LINKER_FLAGS -L${J2K_DIR}/bin -lopenjp2" +fi + +CONFIGURE_ARGS_WEBCODECS="" +if [ "$ENABLE_WEBCODECS" = "1" ]; then + CONFIGURE_ARGS_WEBCODECS="-DWITH_WEBCODECS=ON" +fi + +CONFIGURE_ARGS_UNCOMPRESSED="" +if [ "$ENABLE_UNCOMPRESSED" = "1" ]; then + CONFIGURE_ARGS_UNCOMPRESSED="-DWITH_UNCOMPRESSED_CODEC=ON" +fi + +EXTRA_EXE_LINKER_FLAGS="-lembind" +EXTRA_COMPILER_FLAGS="" +if [ "$STANDALONE" = "1" ]; then + EXTRA_EXE_LINKER_FLAGS="" + EXTRA_COMPILER_FLAGS="-D__EMSCRIPTEN_STANDALONE_WASM__=1" +fi + +CONFIGURE_ARGS="-DENABLE_MULTITHREADING_SUPPORT=OFF -DWITH_GDK_PIXBUF=OFF -DWITH_EXAMPLES=OFF -DBUILD_SHARED_LIBS=OFF -DENABLE_PLUGIN_LOADING=OFF" +emcmake cmake ${SRCDIR} $CONFIGURE_ARGS \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_C_FLAGS="${EXTRA_COMPILER_FLAGS}" \ + -DCMAKE_CXX_FLAGS="${EXTRA_COMPILER_FLAGS}" \ + -DCMAKE_EXE_LINKER_FLAGS="${LIBRARY_LINKER_FLAGS} ${EXTRA_EXE_LINKER_FLAGS}" \ + $CONFIGURE_ARGS_LIBDE265 \ + $CONFIGURE_ARGS_AOM \ + $CONFIGURE_ARGS_WEBCODECS \ + $CONFIGURE_ARGS_UNCOMPRESSED \ + $CONFIGURE_ARGS_OPENJPEG + +VERBOSE=1 emmake make -j${CORES} + +LIBHEIFA="libheif/libheif.a" +EXPORTED_FUNCTIONS=$($EMSDK/upstream/bin/llvm-nm $LIBHEIFA --format=just-symbols | grep "^heif_\|^de265_\|^aom_" | grep "[^:]$" | sed 's/^/_/' | paste -sd "," -) echo "Running Emscripten..." -emcc libheif/.libs/libheif.so \ - --bind \ - -s NO_EXIT_RUNTIME=1 \ - -s TOTAL_MEMORY=${TOTAL_MEMORY} \ - -s ALLOW_MEMORY_GROWTH=1 \ - -s ASSERTIONS=0 \ - -s INVOKE_RUN=0 \ - -s DOUBLE_MODE=0 \ - -s PRECISE_F32=0 \ - -s PRECISE_I64_MATH=0 \ - -s DISABLE_EXCEPTION_CATCHING=1 \ - -s USE_CLOSURE_COMPILER=0 \ - -s LEGACY_VM_SUPPORT=1 \ - --memory-init-file 0 \ - -O3 \ + +BUILD_FLAGS="-lembind -o libheif.js --post-js ${SRCDIR}/post.js -sWASM=$USE_WASM -sDYNAMIC_EXECUTION=$USE_UNSAFE_EVAL" + +if [ "$ENABLE_WEBCODECS" = "1" ]; then + BUILD_FLAGS="$BUILD_FLAGS -sASYNCIFY -sASYNCIFY_IMPORTS=['decode_with_browser_hevc']" +fi + +if [ "$USE_TYPESCRIPT" = "1" ]; then + BUILD_FLAGS="$BUILD_FLAGS --emit-tsd libheif.d.ts" +fi + +RELEASE_BUILD_FLAGS="-O3" + +if [ "$STANDALONE" = "1" ]; then + # Note: this intentionally overwrites the BUILD_FLAGS set above + echo "Building in standalone (non-web) build mode" + BUILD_FLAGS="-sSTANDALONE_WASM -sWASM -o libheif.wasm --no-entry" +fi + +if [ "$DEBUG" = "1" ]; then + echo "Building in debug mode" + RELEASE_BUILD_FLAGS="--profile -g" +fi + +if [ "$USE_ES6" = "1" ]; then + BUILD_FLAGS="$BUILD_FLAGS -sEXPORT_ES6" +fi + +emcc -Wl,--whole-archive "$LIBHEIFA" -Wl,--no-whole-archive \ + -sEXPORTED_FUNCTIONS="$EXPORTED_FUNCTIONS,_free,_malloc,_memcpy" \ + -sMODULARIZE \ + -sEXPORT_NAME="libheif" \ + -sWASM_ASYNC_COMPILATION=0 \ + -sALLOW_MEMORY_GROWTH \ -std=c++11 \ - -L${DIR}/libde265-${LIBDE265_VERSION}/libde265/.libs \ - -lde265 \ - --pre-js pre.js \ - --post-js post.js \ - -o libheif.js + $LIBRARY_INCLUDE_FLAGS \ + $LIBRARY_LINKER_FLAGS \ + $BUILD_FLAGS \ + $RELEASE_BUILD_FLAGS diff --git a/cmake/modules/FindAOM.cmake b/cmake/modules/FindAOM.cmake new file mode 100644 index 0000000000..a357cf7721 --- /dev/null +++ b/cmake/modules/FindAOM.cmake @@ -0,0 +1,53 @@ +find_package(AOM QUIET CONFIG) + +if(TARGET AOM::aom) + if(NOT AOM_FIND_QUIETLY) + message(STATUS "Found AOM: ${AOM_DIR}") + endif() +else() + include(LibFindMacros) + + libfind_pkg_check_modules(AOM_PKGCONF aom) + + find_path(AOM_INCLUDE_DIR + NAMES aom/aom_decoder.h aom/aom_encoder.h + HINTS ${AOM_PKGCONF_INCLUDE_DIRS} ${AOM_PKGCONF_INCLUDEDIR} + PATH_SUFFIXES AOM + ) + + find_library(AOM_LIBRARY + NAMES libaom aom + HINTS ${AOM_PKGCONF_LIBRARY_DIRS} ${AOM_PKGCONF_LIBDIR} + ) + + set(AOM_PROCESS_LIBS AOM_LIBRARY) + set(AOM_PROCESS_INCLUDES AOM_INCLUDE_DIR) + libfind_process(AOM) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(AOM + REQUIRED_VARS + AOM_INCLUDE_DIR + AOM_LIBRARY + ) +endif() + +if(AOM_FOUND) + include(CheckSymbolExists) + + list(APPEND CMAKE_REQUIRED_INCLUDES ${AOM_INCLUDE_DIRS}) + check_symbol_exists(AOM_USAGE_GOOD_QUALITY aom/aom_encoder.h aom_usage_flag_exists) + unset(CMAKE_REQUIRED_INCLUDES) + + if(EXISTS "${AOM_INCLUDE_DIRS}/aom/aom_decoder.h") + set(AOM_DECODER_FOUND YES) + else() + set(AOM_DECODER_FOUND NO) + endif() + + if((EXISTS "${AOM_INCLUDE_DIRS}/aom/aom_encoder.h") AND "${aom_usage_flag_exists}") + set(AOM_ENCODER_FOUND YES) + else() + set(AOM_ENCODER_FOUND NO) + endif() +endif() diff --git a/cmake/modules/FindBrotli.cmake b/cmake/modules/FindBrotli.cmake new file mode 100644 index 0000000000..d0ea34c892 --- /dev/null +++ b/cmake/modules/FindBrotli.cmake @@ -0,0 +1,26 @@ +include(FindPackageHandleStandardArgs) + +find_path(BROTLI_DEC_INCLUDE_DIR "brotli/decode.h") +find_path(BROTLI_ENC_INCLUDE_DIR "brotli/encode.h") + +find_library(BROTLI_COMMON_LIB NAMES brotlicommon) +find_library(BROTLI_DEC_LIB NAMES brotlidec) +find_library(BROTLI_ENC_LIB NAMES brotlienc) + +find_package_handle_standard_args(Brotli + FOUND_VAR + Brotli_FOUND + REQUIRED_VARS + BROTLI_COMMON_LIB + BROTLI_DEC_INCLUDE_DIR + BROTLI_DEC_LIB + BROTLI_ENC_INCLUDE_DIR + BROTLI_ENC_LIB + FAIL_MESSAGE + "Did not find Brotli" +) + + +set(HAVE_BROTLI ${Brotli_FOUND}) +set(BROTLI_INCLUDE_DIRS ${BROTLI_DEC_INCLUDE_DIR} ${BROTLI_ENC_INCLUDE_DIR}) +set(BROTLI_LIBS "${BROTLICOMMON_LIBRARY}" "${BROTLI_DEC_LIB}" "${BROTLI_ENC_LIB}") \ No newline at end of file diff --git a/cmake/modules/FindDav1d.cmake b/cmake/modules/FindDAV1D.cmake similarity index 100% rename from cmake/modules/FindDav1d.cmake rename to cmake/modules/FindDAV1D.cmake diff --git a/cmake/modules/FindFFMPEG.cmake b/cmake/modules/FindFFMPEG.cmake new file mode 100644 index 0000000000..f777aa70be --- /dev/null +++ b/cmake/modules/FindFFMPEG.cmake @@ -0,0 +1,198 @@ +# Taken Oct.2023 from VTK (Visualization Toolkit) + +#[==[ +Provides the following variables: + + * `FFMPEG_INCLUDE_DIRS`: Include directories necessary to use FFMPEG. + * `FFMPEG_LIBRARIES`: Libraries necessary to use FFMPEG. Note that this only + includes libraries for the components requested. + * `FFMPEG_VERSION`: The version of FFMPEG found. + +The following components are supported: + + * `avcodec` + * `avdevice` + * `avfilter` + * `avformat` + * `avresample` + * `avutil` + * `swresample` + * `swscale` + +For each component, the following are provided: + + * `FFMPEG__FOUND`: Libraries for the component. + * `FFMPEG__INCLUDE_DIRS`: Include directories for + the component. + * `FFMPEG__LIBRARIES`: Libraries for the component. + * `FFMPEG::`: A target to use with `target_link_libraries`. + +Note that only components requested with `COMPONENTS` or `OPTIONAL_COMPONENTS` +are guaranteed to set these variables or provide targets. +#]==] + +function (_ffmpeg_find component headername) + find_path("FFMPEG_${component}_INCLUDE_DIR" + NAMES + "lib${component}/${headername}" + PATHS + "${FFMPEG_ROOT}/include" + ~/Library/Frameworks + /Library/Frameworks + /usr/local/include + /usr/include + /sw/include # Fink + /opt/local/include # DarwinPorts + /opt/csw/include # Blastwave + /opt/include + /usr/freeware/include + PATH_SUFFIXES + ffmpeg + DOC "FFMPEG's ${component} include directory") + mark_as_advanced("FFMPEG_${component}_INCLUDE_DIR") + + # On Windows, static FFMPEG is sometimes built as `lib.a`. + if (WIN32) + list(APPEND CMAKE_FIND_LIBRARY_SUFFIXES ".a" ".lib") + list(APPEND CMAKE_FIND_LIBRARY_PREFIXES "" "lib") + endif () + + find_library("FFMPEG_${component}_LIBRARY" + NAMES + "${component}" + PATHS + "${FFMPEG_ROOT}/lib" + ~/Library/Frameworks + /Library/Frameworks + /usr/local/lib + /usr/local/lib64 + /usr/lib + /usr/lib64 + /sw/lib + /opt/local/lib + /opt/csw/lib + /opt/lib + /usr/freeware/lib64 + "${FFMPEG_ROOT}/bin" + DOC "FFMPEG's ${component} library") + mark_as_advanced("FFMPEG_${component}_LIBRARY") + + if (FFMPEG_${component}_LIBRARY AND FFMPEG_${component}_INCLUDE_DIR) + set(_deps_found TRUE) + set(_deps_link) + foreach (_ffmpeg_dep IN LISTS ARGN) + if (TARGET "FFMPEG::${_ffmpeg_dep}") + list(APPEND _deps_link "FFMPEG::${_ffmpeg_dep}") + else () + set(_deps_found FALSE) + endif () + endforeach () + if (_deps_found) + if (NOT TARGET "FFMPEG::${component}") + add_library("FFMPEG::${component}" UNKNOWN IMPORTED) + set_target_properties("FFMPEG::${component}" PROPERTIES + IMPORTED_LOCATION "${FFMPEG_${component}_LIBRARY}" + INTERFACE_INCLUDE_DIRECTORIES "${FFMPEG_${component}_INCLUDE_DIR}" + IMPORTED_LINK_INTERFACE_LIBRARIES "${_deps_link}") + endif () + set("FFMPEG_${component}_FOUND" 1 + PARENT_SCOPE) + + set(version_header_path "${FFMPEG_${component}_INCLUDE_DIR}/lib${component}/version.h") + if (EXISTS "${version_header_path}") + string(TOUPPER "${component}" component_upper) + file(STRINGS "${version_header_path}" version + REGEX "#define *LIB${component_upper}_VERSION_(MAJOR|MINOR|MICRO) ") + string(REGEX REPLACE ".*_MAJOR *\([0-9]*\).*" "\\1" major "${version}") + string(REGEX REPLACE ".*_MINOR *\([0-9]*\).*" "\\1" minor "${version}") + string(REGEX REPLACE ".*_MICRO *\([0-9]*\).*" "\\1" micro "${version}") + if (NOT major STREQUAL "" AND + NOT minor STREQUAL "" AND + NOT micro STREQUAL "") + set("FFMPEG_${component}_VERSION" "${major}.${minor}.${micro}" + PARENT_SCOPE) + endif () + endif () + else () + set("FFMPEG_${component}_FOUND" 0 + PARENT_SCOPE) + set(what) + if (NOT FFMPEG_${component}_LIBRARY) + set(what "library") + endif () + if (NOT FFMPEG_${component}_INCLUDE_DIR) + if (what) + string(APPEND what " or headers") + else () + set(what "headers") + endif () + endif () + set("FFMPEG_${component}_NOT_FOUND_MESSAGE" + "Could not find the ${what} for ${component}." + PARENT_SCOPE) + endif () + endif () +endfunction () + +_ffmpeg_find(avutil avutil.h) +_ffmpeg_find(avresample avresample.h + avutil) +_ffmpeg_find(swresample swresample.h + avutil) +_ffmpeg_find(swscale swscale.h + avutil) +_ffmpeg_find(avcodec avcodec.h + avutil) +_ffmpeg_find(avformat avformat.h + avcodec avutil) +_ffmpeg_find(avfilter avfilter.h + avutil) +_ffmpeg_find(avdevice avdevice.h + avformat avutil) + +if (TARGET FFMPEG::avutil) + set(_ffmpeg_version_header_path "${FFMPEG_avutil_INCLUDE_DIR}/libavutil/ffversion.h") + if (EXISTS "${_ffmpeg_version_header_path}") + file(STRINGS "${_ffmpeg_version_header_path}" _ffmpeg_version + REGEX "FFMPEG_VERSION") + string(REGEX REPLACE ".*\"n?\(.*\)\"" "\\1" FFMPEG_VERSION "${_ffmpeg_version}") + unset(_ffmpeg_version) + else () + set(FFMPEG_VERSION FFMPEG_VERSION-NOTFOUND) + endif () + unset(_ffmpeg_version_header_path) +endif () + +set(FFMPEG_INCLUDE_DIRS) +set(FFMPEG_LIBRARIES) +set(_ffmpeg_required_vars) +foreach (_ffmpeg_component IN LISTS FFMPEG_FIND_COMPONENTS) + if (TARGET "FFMPEG::${_ffmpeg_component}") + set(FFMPEG_${_ffmpeg_component}_INCLUDE_DIRS + "${FFMPEG_${_ffmpeg_component}_INCLUDE_DIR}") + set(FFMPEG_${_ffmpeg_component}_LIBRARIES + "${FFMPEG_${_ffmpeg_component}_LIBRARY}") + list(APPEND FFMPEG_INCLUDE_DIRS + "${FFMPEG_${_ffmpeg_component}_INCLUDE_DIRS}") + list(APPEND FFMPEG_LIBRARIES + "${FFMPEG_${_ffmpeg_component}_LIBRARIES}") + if (FFMEG_FIND_REQUIRED_${_ffmpeg_component}) + list(APPEND _ffmpeg_required_vars + "FFMPEG_${_ffmpeg_required_vars}_INCLUDE_DIRS" + "FFMPEG_${_ffmpeg_required_vars}_LIBRARIES") + endif () + endif () +endforeach () +unset(_ffmpeg_component) + +if (FFMPEG_INCLUDE_DIRS) + list(REMOVE_DUPLICATES FFMPEG_INCLUDE_DIRS) +endif () + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(FFMPEG + REQUIRED_VARS FFMPEG_INCLUDE_DIRS FFMPEG_LIBRARIES ${_ffmpeg_required_vars} + VERSION_VAR FFMPEG_VERSION + HANDLE_COMPONENTS) +unset(_ffmpeg_required_vars) + diff --git a/cmake/modules/FindLibde265.cmake b/cmake/modules/FindLIBDE265.cmake similarity index 100% rename from cmake/modules/FindLibde265.cmake rename to cmake/modules/FindLIBDE265.cmake diff --git a/cmake/modules/FindLibAOM.cmake b/cmake/modules/FindLibAOM.cmake deleted file mode 100644 index 4e009a201c..0000000000 --- a/cmake/modules/FindLibAOM.cmake +++ /dev/null @@ -1,37 +0,0 @@ -include(LibFindMacros) - -libfind_pkg_check_modules(AOM_PKGCONF aom) - -find_path(AOM_INCLUDE_DIR - NAMES aom/aom_decoder.h aom/aom_encoder.h - HINTS ${AOM_PKGCONF_INCLUDE_DIRS} ${AOM_PKGCONF_INCLUDEDIR} - PATH_SUFFIXES AOM -) - -find_library(AOM_LIBRARY - NAMES libaom aom - HINTS ${AOM_PKGCONF_LIBRARY_DIRS} ${AOM_PKGCONF_LIBDIR} -) - -if(EXISTS "${AOM_INCLUDE_DIR}/aom/aom_decoder.h") - set(AOM_DECODER_FOUND YES) -else() - set(AOM_DECODER_FOUND NO) -endif() - -if(EXISTS "${AOM_INCLUDE_DIR}/aom/aom_encoder.h") - set(AOM_ENCODER_FOUND YES) -else() - set(AOM_ENCODER_FOUND NO) -endif() - -set(AOM_PROCESS_LIBS AOM_LIBRARY) -set(AOM_PROCESS_INCLUDES AOM_INCLUDE_DIR) -libfind_process(AOM) - -include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(AOM - REQUIRED_VARS - AOM_INCLUDE_DIR - AOM_LIBRARIES -) diff --git a/cmake/modules/FindOPENJPH.cmake b/cmake/modules/FindOPENJPH.cmake new file mode 100644 index 0000000000..7edb122142 --- /dev/null +++ b/cmake/modules/FindOPENJPH.cmake @@ -0,0 +1,33 @@ +find_package(OPENJPH QUIET CONFIG NAMES openjph) + +if(TARGET openjph) + if(NOT OPENJPH_FIND_QUIETLY) + message(STATUS "Found openjph: ${OPENJPH_DIR}") + endif() + set(OPENJPH_LIBRARIES "openjph") +else() + include(LibFindMacros) + libfind_pkg_check_modules(OPENJPH_PKGCONF openjph) + + find_path(OPENJPH_INCLUDE_DIR + NAMES openjph/ojph_version.h + HINTS ${OPENJPH_PKGCONF_INCLUDE_DIRS} ${OPENJPH_PKGCONF_INCLUDEDIR} + PATH_SUFFIXES OPENJPH + ) + + find_library(OPENJPH_LIBRARY + NAMES libopenjph openjph + HINTS ${OPENJPH_PKGCONF_LIBRARY_DIRS} ${OPENJPH_PKGCONF_LIBDIR} + ) + + set(OPENJPH_PROCESS_LIBS OPENJPH_LIBRARY) + set(OPENJPH_PROCESS_INCLUDES OPENJPH_INCLUDE_DIR) + libfind_process(OPENJPH) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(OPENJPH + REQUIRED_VARS + OPENJPH_INCLUDE_DIR + OPENJPH_LIBRARY + ) +endif() diff --git a/cmake/modules/FindOpenH264.cmake b/cmake/modules/FindOpenH264.cmake new file mode 100644 index 0000000000..a4933249ea --- /dev/null +++ b/cmake/modules/FindOpenH264.cmake @@ -0,0 +1,26 @@ +include(LibFindMacros) +include(CheckSymbolExists) + +libfind_pkg_check_modules(OpenH264_PKGCONF openh264) + +find_path(OpenH264_INCLUDE_DIR + NAMES wels/codec_api.h + HINTS ${OpenH264_PKGCONF_INCLUDE_DIRS} ${OpenH264_PKGCONF_INCLUDEDIR} + PATH_SUFFIXES OpenH264 +) + +find_library(OpenH264_LIBRARY + NAMES libopenh264 openh264 + HINTS ${OpenH264_PKGCONF_LIBRARY_DIRS} ${OpenH264_PKGCONF_LIBDIR} +) + +set(OpenH264_PROCESS_LIBS OpenH264_LIBRARY) +set(OpenH264_PROCESS_INCLUDES OpenH264_INCLUDE_DIR) +libfind_process(OpenH264) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(OpenH264 + REQUIRED_VARS + OpenH264_INCLUDE_DIR + OpenH264_LIBRARIES +) diff --git a/cmake/modules/FindRav1e.cmake b/cmake/modules/FindRAV1E.cmake similarity index 95% rename from cmake/modules/FindRav1e.cmake rename to cmake/modules/FindRAV1E.cmake index 5f2435247e..3012c4ad59 100644 --- a/cmake/modules/FindRav1e.cmake +++ b/cmake/modules/FindRAV1E.cmake @@ -4,7 +4,7 @@ libfind_pkg_check_modules(RAV1E_PKGCONF rav1e) find_path(RAV1E_INCLUDE_DIR NAMES rav1e.h HINTS ${RAV1E_PKGCONF_INCLUDE_DIRS} ${RAV1E_PKGCONF_INCLUDEDIR} - PATH_SUFFIXES RAV1E + PATH_SUFFIXES RAV1E rav1e ) find_library(RAV1E_LIBRARY diff --git a/cmake/modules/FindSvtEnc.cmake b/cmake/modules/FindSvtEnc.cmake new file mode 100644 index 0000000000..b0d230ab1a --- /dev/null +++ b/cmake/modules/FindSvtEnc.cmake @@ -0,0 +1,24 @@ +include(LibFindMacros) +libfind_pkg_check_modules(SvtEnc_PKGCONF SvtAv1Enc) + +find_path(SvtEnc_INCLUDE_DIR + NAMES svt-av1/EbSvtAv1Enc.h + HINTS ${SvtEnc_PKGCONF_INCLUDE_DIRS} ${SvtEnc_PKGCONF_INCLUDEDIR} + PATH_SUFFIXES SvtEnc +) + +find_library(SvtEnc_LIBRARY + NAMES SvtAv1Enc libSvtAv1Enc + HINTS ${SvtEnc_PKGCONF_LIBRARY_DIRS} ${SvtEnc_PKGCONF_LIBDIR} +) + +set(SvtEnc_PROCESS_LIBS SvtEnc_LIBRARY) +set(SvtEnc_PROCESS_INCLUDES SvtEnc_INCLUDE_DIR) +libfind_process(SvtEnc) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(SvtEnc + REQUIRED_VARS + SvtEnc_INCLUDE_DIR + SvtEnc_LIBRARIES +) diff --git a/cmake/modules/FindUVG266.cmake b/cmake/modules/FindUVG266.cmake new file mode 100644 index 0000000000..ba2a7ec056 --- /dev/null +++ b/cmake/modules/FindUVG266.cmake @@ -0,0 +1,24 @@ +include(LibFindMacros) +libfind_pkg_check_modules(UVG266_PKGCONF uvg266) + +find_path(UVG266_INCLUDE_DIR + NAMES uvg266.h + HINTS ${UVG266_PKGCONF_INCLUDE_DIRS} ${UVG266_PKGCONF_INCLUDEDIR} + PATH_SUFFIXES UVG266 uvg266 +) + +find_library(UVG266_LIBRARY + NAMES libuvg266 uvg266 uvg266.dll + HINTS ${UVG266_PKGCONF_LIBRARY_DIRS} ${UVG266_PKGCONF_LIBDIR} +) + +set(UVG266_PROCESS_LIBS UVG266_LIBRARY) +set(UVG266_PROCESS_INCLUDES UVG266_INCLUDE_DIR) +libfind_process(UVG266) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(UVG266 + REQUIRED_VARS + UVG266_INCLUDE_DIR + UVG266_LIBRARIES +) diff --git a/cmake/modules/FindX264.cmake b/cmake/modules/FindX264.cmake new file mode 100644 index 0000000000..0d40c2bda3 --- /dev/null +++ b/cmake/modules/FindX264.cmake @@ -0,0 +1,38 @@ +include(LibFindMacros) +libfind_pkg_check_modules(X264_PKGCONF x264) + +find_path(X264_INCLUDE_DIR + NAMES x264.h + HINTS ${X264_PKGCONF_INCLUDE_DIRS} ${X264_PKGCONF_INCLUDEDIR} + PATH_SUFFIXES X264 +) + +find_library(X264_LIBRARY + NAMES libx264 x264 + HINTS ${X264_PKGCONF_LIBRARY_DIRS} ${X264_PKGCONF_LIBDIR} +) + +set(X264_PROCESS_LIBS X264_LIBRARY) +set(X264_PROCESS_INCLUDES X264_INCLUDE_DIR) +libfind_process(X264) + +if(X264_INCLUDE_DIR) + set(x264_config_file "${X264_INCLUDE_DIR}/x264_config.h") + if(EXISTS ${x264_config_file}) + file(STRINGS + ${x264_config_file} + TMP + REGEX "#define X264_BUILD .*$") + string(REGEX REPLACE "#define X264_BUILD" "" TMP "${TMP}") + string(REGEX MATCHALL "[0-9.]+" X264_BUILD "${TMP}") + endif() +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(X264 + REQUIRED_VARS + X264_INCLUDE_DIR + X264_LIBRARIES + VERSION_VAR + X264_BUILD +) diff --git a/cmake/modules/Findkvazaar.cmake b/cmake/modules/Findkvazaar.cmake new file mode 100644 index 0000000000..90f0c1d723 --- /dev/null +++ b/cmake/modules/Findkvazaar.cmake @@ -0,0 +1,36 @@ +include(LibFindMacros) +include(CheckStructHasMember) +include(CheckSymbolExists) + +libfind_pkg_check_modules(KVAZAAR_PKGCONF kvazaar) + +find_path(KVAZAAR_INCLUDE_DIR + NAMES kvazaar.h + HINTS ${KVAZAAR_PKGCONF_INCLUDE_DIRS} ${KVAZAAR_PKGCONF_INCLUDEDIR} + PATH_SUFFIXES KVAZAAR kvazaar +) + +find_library(KVAZAAR_LIBRARY + NAMES libkvazaar kvazaar kvazaar.dll + HINTS ${KVAZAAR_PKGCONF_LIBRARY_DIRS} ${KVAZAAR_PKGCONF_LIBDIR} +) + +set(KVAZAAR_PROCESS_LIBS KVAZAAR_LIBRARY) +set(KVAZAAR_PROCESS_INCLUDES KVAZAAR_INCLUDE_DIR) +libfind_process(KVAZAAR) + +set(CMAKE_REQUIRED_INCLUDES ${KVAZAAR_INCLUDE_DIR}) +set(CMAKE_REQUIRED_LIBRARIES ${KVAZAAR_LIBRARY}) +CHECK_STRUCT_HAS_MEMBER("struct kvz_config" enable_logging_output kvazaar.h + HAVE_KVAZAAR_ENABLE_LOGGING LANGUAGE CXX) +check_symbol_exists(kvz_get_version_string kvazaar.h + HAVE_KVAZAAR_VERSION_STRING) +unset(CMAKE_REQUIRED_INCLUDES) +unset(CMAKE_REQUIRED_LIBRARIES) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(kvazaar + REQUIRED_VARS + KVAZAAR_INCLUDE_DIR + KVAZAAR_LIBRARIES +) diff --git a/cmake/modules/Findlibsharpyuv.cmake b/cmake/modules/Findlibsharpyuv.cmake new file mode 100644 index 0000000000..c6f2336e91 --- /dev/null +++ b/cmake/modules/Findlibsharpyuv.cmake @@ -0,0 +1,25 @@ +include(LibFindMacros) + +libfind_pkg_check_modules(LIBSHARPYUV_PKGCONF libsharpyuv) + +find_path(LIBSHARPYUV_INCLUDE_DIR + NAMES sharpyuv/sharpyuv.h + HINTS ${LIBSHARPYUV_PKGCONF_INCLUDE_DIRS} ${LIBSHARPYUV_PKGCONF_INCLUDEDIR} + PATH_SUFFIXES LIBSHARPYUV +) + +find_library(LIBSHARPYUV_LIBRARY + NAMES sharpyuv + HINTS ${LIBSHARPYUV_PKGCONF_LIBRARY_DIRS} ${LIBSHARPYUV_PKGCONF_LIBDIR} +) + +set(LIBSHARPYUV_PROCESS_LIBS LIBSHARPYUV_LIBRARY) +set(LIBSHARPYUV_PROCESS_INCLUDES LIBSHARPYUV_INCLUDE_DIR) +libfind_process(LIBSHARPYUV) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(libsharpyuv + REQUIRED_VARS + LIBSHARPYUV_INCLUDE_DIR + LIBSHARPYUV_LIBRARIES +) diff --git a/cmake/modules/LibFindMacros.cmake b/cmake/modules/LibFindMacros.cmake index e293efff89..3d1f2459f8 100644 --- a/cmake/modules/LibFindMacros.cmake +++ b/cmake/modules/LibFindMacros.cmake @@ -1,7 +1,7 @@ # Version 2.3 # Public Domain, originally written by Lasse Kärkkäinen # Maintained at https://github.com/Tronic/cmake-modules -# Please send your improvements as pull requests on Github. +# Please send your improvements as pull requests on GitHub. # Find another package and make it a dependency of the current package. # This also automatically forwards the "REQUIRED" argument. diff --git a/configure.ac b/configure.ac deleted file mode 100644 index 8f0c1e96b3..0000000000 --- a/configure.ac +++ /dev/null @@ -1,335 +0,0 @@ -AC_PREREQ([2.68]) -AC_INIT([libheif], [1.12.0], [opensource@struktur.de]) -AC_CONFIG_SRCDIR([libheif/box.cc]) -AC_CONFIG_HEADERS([config.h]) - -# Note: do not forget to set the version in the CMakeLists.txt file accordingly -PROJECT_VERSION_MAJOR=1 -PROJECT_VERSION_MINOR=12 -PROJECT_VERSION_PATCH=0 -PROJECT_VERSION_TWEAK=0 -AC_SUBST(PROJECT_VERSION_MAJOR) -AC_SUBST(PROJECT_VERSION_MINOR) -AC_SUBST(PROJECT_VERSION_PATCH) -AC_SUBST(PROJECT_VERSION_TWEAK) - -# From https://www.gnu.org/software/libtool/manual/html_node/Updating-version-info.html: -# If the library source code has changed at all since the last update, then increment revision (‘c:r:a’ becomes ‘c:r+1:a’). -# If any interfaces have been added, removed, or changed since the last update, increment current, and set revision to 0. -# If any interfaces have been added since the last public release, then increment age. -# If any interfaces have been removed or changed since the last public release, then set age to 0. - -LIBHEIF_CURRENT=13 -LIBHEIF_REVISION=0 -LIBHEIF_AGE=12 -AC_SUBST(LIBHEIF_CURRENT) -AC_SUBST(LIBHEIF_REVISION) -AC_SUBST(LIBHEIF_AGE) - -AC_CANONICAL_SYSTEM -LT_INIT -AC_CONFIG_MACRO_DIR([m4]) -AC_CONFIG_AUX_DIR([.]) - -# Checks for programs. -AM_PROG_AS -AC_PROG_CXX -AC_PROG_CC -AC_PROG_INSTALL -AC_PROG_LN_S -AC_PROG_GREP -AC_ARG_ENABLE([go], AS_HELP_STRING([--disable-go], - [Disable building Go code / examples.])) -if eval "test x$enable_go = x" ; then - AC_PATH_PROG([GO], [go], [no], [$PATH$PATH_SEPARATOR/usr/lib/go-1.10$PATH_SEPARATOR/usr/lib/go-1.9$PATH_SEPARATOR/usr/lib/go-1.8$PATH_SEPARATOR/usr/lib/go-1.7$PATH_SEPARATOR/usr/lib/go-1.6]) - AC_SUBST(GO) -fi -AM_CONDITIONAL([HAVE_GO], [test "x$GO" != "xno" && test "x$GO" != "x"]) -AC_SUBST(HAVE_GO) - -AC_ARG_ENABLE([examples], AS_HELP_STRING([--disable-examples], - [Disable building examples.]), [], [enable_examples=yes]) -AM_CONDITIONAL([WITH_EXAMPLES], [test "x$enable_examples" = "xyes"]) -AC_SUBST(WITH_EXAMPLES) - -AM_CONDITIONAL(MINGW, expr $host : '.*-mingw' >/dev/null 2>&1) - -AM_INIT_AUTOMAKE([1.13 foreign]) -AM_EXTRA_RECURSIVE_TARGETS([format test]) - -AX_CXX_COMPILE_STDCXX_11() - -AC_HEADER_STDBOOL -AC_CHECK_HEADERS([inttypes.h stddef.h unistd.h]) -AC_C_INLINE -AC_FUNC_ERROR_AT_LINE - -AC_TYPE_SIZE_T -AC_TYPE_UINT8_T -AC_TYPE_UINT16_T -AC_TYPE_UINT32_T -AC_TYPE_UINT64_T - -AC_CHECK_LIB([pthread], [pthread_create]) - -AC_ARG_ENABLE([visibility], AS_HELP_STRING([--disable-visibility], - [Disable visibility definitions.]), - [HAVE_VISIBILITY=0], - [gl_VISIBILITY]) - -AC_ARG_ENABLE([tests], AS_HELP_STRING([--enable-tests], - [Enable compiling tests ('visibility' will be disabled).])) -AS_IF([test "x$enable_tests" = "xyes"], [ - HAVE_TESTS=1 - enable_visibility=no - HAVE_VISIBILITY=0 - CFLAG_VISIBILITY= -]) -AM_CONDITIONAL([HAVE_TESTS], [test "x$HAVE_TESTS" == "x1"]) - -AM_CONDITIONAL([HAVE_VISIBILITY], [test "x$HAVE_VISIBILITY" != "x0"]) -if eval "test x$enable_visibility = x" ; then enable_visibility=yes ; fi - -REQUIRES_PRIVATE="" -AC_SUBST([REQUIRES_PRIVATE]) - -PKG_PROG_PKG_CONFIG - -have_avif_decoder="no" -have_avif_encoder="no" - -AC_ARG_ENABLE([aom], AS_HELP_STRING([--disable-aom], - [Disable building of aom decoder/encoder.])) -if eval "test x$enable_aom = x" ; then enable_aom=yes ; fi -if eval "test x$enable_aom != xno"; then - PKG_CHECK_MODULES([aom], [aom], [ - AC_DEFINE([HAVE_AOM], [1], [Whether aom was found.]) - AC_SUBST(aom_CFLAGS) - AC_SUBST(aom_LIBS) - REQUIRES_PRIVATE="$REQUIRES_PRIVATE aom" - have_aom="yes" - ], [have_aom="no"]) - - if eval "test x$have_aom = xyes"; then - AC_CHECK_HEADER([aom/aom_decoder.h], [ - AC_DEFINE([HAVE_AOM_DECODER], [1], [Whether aom decoder was found.]) - have_aom_decoder="yes" - have_avif_decoder="yes" - ], [have_aom_decoder="no"]) - - AC_CHECK_HEADER([aom/aom_encoder.h], [ - AC_DEFINE([HAVE_AOM_ENCODER], [1], [Whether aom encoder was found.]) - have_aom_encoder="yes" - have_avif_encoder="yes" - ], [have_aom_encoder="no"]) - fi -else - have_aom="no" - have_aom_decoder="no" - have_aom_encoder="no" -fi -AM_CONDITIONAL([HAVE_AOM], [test "x$have_aom" = "xyes"]) -AC_SUBST([have_aom]) -AM_CONDITIONAL([HAVE_AOM_DECODER], [test "x$have_aom_decoder" = "xyes"]) -AC_SUBST([have_aom_decoder]) -AM_CONDITIONAL([HAVE_AOM_ENCODER], [test "x$have_aom_encoder" = "xyes"]) -AC_SUBST([have_aom_encoder]) - -AC_ARG_ENABLE([libde265], AS_HELP_STRING([--disable-libde265], - [Disable building of libde265 decoder.])) -if eval "test x$enable_libde265 = x" ; then enable_libde265=yes ; fi -if eval "test x$enable_libde265 != xno"; then - PKG_CHECK_MODULES([libde265], [libde265], [ - AC_DEFINE([HAVE_LIBDE265], [1], [Whether libde265 was found.]) - AC_SUBST(libde265_CFLAGS) - AC_SUBST(libde265_LIBS) - REQUIRES_PRIVATE="$REQUIRES_PRIVATE libde265" - have_libde265="yes" - ], [have_libde265="no"]) -else - have_libde265="no" -fi -AM_CONDITIONAL([HAVE_LIBDE265], [test "x$have_libde265" = "xyes"]) -AC_SUBST([have_libde265]) - -AC_ARG_ENABLE([x265], AS_HELP_STRING([--disable-x265], - [Disable building of x265 encoder.])) -if eval "test x$enable_x265 = x" ; then enable_x265=yes ; fi -if eval "test x$enable_x265 != xno"; then - PKG_CHECK_MODULES([x265], [x265], [ - AC_DEFINE([HAVE_X265], [1], [Whether x265 was found.]) - AC_SUBST(x265_CFLAGS) - AC_SUBST(x265_LIBS) - REQUIRES_PRIVATE="$REQUIRES_PRIVATE x265" - have_x265="yes" - ], [have_x265="no"]) -else - have_x265="no" -fi -AM_CONDITIONAL([HAVE_X265], [test "x$have_x265" = "xyes"]) -AC_SUBST([have_x265]) - -AC_CHECK_HEADERS([jpeglib.h]) -AC_CHECK_LIB([jpeg], [jpeg_CreateCompress], [ - AC_DEFINE([HAVE_LIBJPEG], [1], [Whether libjpeg was found.]) - libjpeg_CFLAGS="" - AC_SUBST(libjpeg_CFLAGS) - libjpeg_LIBS="-ljpeg" - AC_SUBST(libjpeg_LIBS) - have_libjpeg="yes" -], [have_libjpeg="no"]) -if eval "test x$have_libjpeg = xno"; then -AC_CHECK_LIB([jpeg], [jpeg_destroy_compress], [ - AC_DEFINE([HAVE_LIBJPEG], [1], [Whether libjpeg was found.]) - libjpeg_CFLAGS="" - AC_SUBST(libjpeg_CFLAGS) - libjpeg_LIBS="-ljpeg" - AC_SUBST(libjpeg_LIBS) - have_libjpeg="yes" -], [have_libjpeg="no"]) -fi -AM_CONDITIONAL([HAVE_LIBJPEG], [test "x$have_libjpeg" = "xyes"]) -if eval "test x$have_libjpeg = xyes"; then -AC_MSG_CHECKING([for jpeg_write_icc_profile]) -AC_LANG_PUSH(C++) -AC_TRY_COMPILE([ - #include - #include - #include -],[ - jpeg_write_icc_profile(NULL, NULL, 0); -],[has_jpeg_write_icc_profile=yes],[has_jpeg_write_icc_profile=no]); -AC_LANG_POP(C++) -AC_MSG_RESULT([$has_jpeg_write_icc_profile]) -if eval "test x$has_jpeg_write_icc_profile = xyes"; then - AC_DEFINE(HAVE_JPEG_WRITE_ICC_PROFILE, 1, [Define to 1 if jpeg_write_icc_profile is available in libjpeg.]) -fi -fi - -PKG_CHECK_MODULES([libpng], [libpng], [ - AC_DEFINE([HAVE_LIBPNG], [1], [Whether libpng was found.]) - AC_SUBST(libpng_CFLAGS) - AC_SUBST(libpng_LIBS) - have_libpng="yes" -], [have_libpng="no"]) -AM_CONDITIONAL([HAVE_LIBPNG], [test "x$have_libpng" = "xyes"]) - -AC_ARG_ENABLE([gdk-pixbuf], AS_HELP_STRING([--disable-gdk-pixbuf], - [Disable building of gdk-pixbuf plugin.])) -if eval "test x$enable_gdk_pixbuf = x" ; then enable_gdk_pixbuf=yes ; fi -if eval "test x$enable_gdk_pixbuf != xno"; then - PKG_CHECK_MODULES([gdkpixbuf], [gdk-pixbuf-2.0], [ - AC_DEFINE([HAVE_GDKPIXBUF2], [1], [Whether gdk-pixbuf-2.0 was found.]) - AC_SUBST(gdkpixbuf_CFLAGS) - AC_SUBST(gdkpixbuf_LIBS) - have_gdkpixbuf2="yes" - ], [have_gdkpixbuf2="no"]) -else - have_gdkpixbuf2="no" -fi -AM_CONDITIONAL([HAVE_GDKPIXBUF2], [test "x$have_gdkpixbuf2" = "xyes"]) - -if eval "test x$have_gdkpixbuf2 = xyes"; then - gdk_pixbuf_binary_version="`$PKG_CONFIG --variable=gdk_pixbuf_binary_version gdk-pixbuf-2.0`" - gdk_pixbuf_binarydir="`$PKG_CONFIG --variable=gdk_pixbuf_binarydir gdk-pixbuf-2.0`" - gdk_pixbuf_moduledir=`$PKG_CONFIG --variable gdk_pixbuf_moduledir gdk-pixbuf-2.0` - gdk_pixbuf_cache_file=`$PKG_CONFIG --variable gdk_pixbuf_cache_file gdk-pixbuf-2.0` - - AC_SUBST([gdk_pixbuf_binary_version]) - AC_SUBST([gdk_pixbuf_binarydir]) - AC_SUBST([gdk_pixbuf_moduledir]) - AC_SUBST([gdk_pixbuf_cache_file]) -fi - -AC_ARG_ENABLE([rav1e], AS_HELP_STRING([--disable-rav1e], - [Disable building of rav1e encoder.])) -if eval "test x$enable_rav1e = x" ; then enable_rav1e=yes ; fi -if eval "test x$enable_rav1e != xno"; then - PKG_CHECK_MODULES([rav1e], [rav1e], [ - AC_DEFINE([HAVE_RAV1E], [1], [Whether rav1e was found.]) - AC_SUBST(rav1e_CFLAGS) - AC_SUBST(rav1e_LIBS) - have_avif_encoder="yes" - REQUIRES_PRIVATE="$REQUIRES_PRIVATE rav1e" - have_rav1e="yes" - ], [have_rav1e="no"]) -else - have_rav1e="no" -fi -AM_CONDITIONAL([HAVE_RAV1E], [test "x$have_rav1e" = "xyes"]) -AC_SUBST([have_rav1e]) - -PKG_CHECK_MODULES([dav1d], [dav1d], [ - AC_DEFINE([HAVE_DAV1D], [1], [Whether dav1d was found.]) - AC_SUBST(dav1d_CFLAGS) - AC_SUBST(dav1d_LIBS) - have_avif_decoder="yes" - REQUIRES_PRIVATE="$REQUIRES_PRIVATE dav1d" - have_dav1d="yes" -], [have_dav1d="no"]) -AM_CONDITIONAL([HAVE_DAV1D], [test "x$have_dav1d" = "xyes"]) - -AC_SUBST(have_avif_decoder) -AC_SUBST(have_avif_encoder) - -AC_ARG_ENABLE([libfuzzer], AS_HELP_STRING([--enable-libfuzzer], - [Enable fuzzing with libFuzzer.]), [], [enable_libfuzzer=no]) -if eval "test x$enable_libfuzzer != xno"; then - AC_MSG_NOTICE([Enable libFuzzer]) - AC_DEFINE([HAVE_LIBFUZZER], [1], [Whether building with libFuzzer.]) - - FUZZING_ENGINE="-lFuzzingEngine" - if eval "test x$enable_libfuzzer != xyes"; then - FUZZING_ENGINE="$enable_libfuzzer" - fi - AC_MSG_NOTICE([Fuzzing engine flags: $FUZZING_ENGINE]) - AC_SUBST([FUZZING_ENGINE]) -fi -AM_CONDITIONAL([ENABLE_LIBFUZZER], [test "x$enable_libfuzzer" != "xno"]) -AC_SUBST(ENABLE_LIBFUZZER) - -AC_ARG_ENABLE([multithreading], AS_HELP_STRING([--disable-multithreading], - [Disable multithreaded decoding.])) -if eval "test x$enable_multithreading = x" ; then enable_multithreading=yes ; fi -if eval "test x$enable_multithreading != xno"; then - AC_MSG_NOTICE([Enable multithreading]) - AC_DEFINE([ENABLE_PARALLEL_TILE_DECODING], [1], [Whether we enable multithreaded decoding.]) -fi -AC_SUBST(ENABLE_PARALLEL_TILE_DECODING) - -WARNING_FLAGS="-Wall -Werror -Wsign-compare -Wconversion -Wno-sign-conversion -Wno-error=conversion -Wno-error=unused-parameter -Wno-error=deprecated-declarations" - -CXXFLAGS="$CXXFLAGS $WARNING_FLAGS" -CFLAGS="$CFLAGS $WARNING_FLAGS" - -AC_C_CXX_COMPILE_FLAGS([-Wno-error=potentially-evaluated-expression]) - -AC_MSG_NOTICE([---------------------------------------]) -AC_MSG_NOTICE([Multithreading: $enable_multithreading]) -AC_MSG_NOTICE([Symbol visibility: $enable_visibility]) -AC_MSG_NOTICE([libaom decoder: $have_aom_decoder]) -AC_MSG_NOTICE([libaom encoder: $have_aom_encoder]) -AC_MSG_NOTICE([rav1e encoder: $have_rav1e]) -AC_MSG_NOTICE([dav1d decoder: $have_dav1d]) -AC_MSG_NOTICE([libde265 decoder: $have_libde265]) -AC_MSG_NOTICE([libx265 encoder: $have_x265]) -AC_MSG_NOTICE([JPEG output: $have_libjpeg]) -AC_MSG_NOTICE([PNG output: $have_libpng]) -AC_MSG_NOTICE([GdkPixbuf2 loader: $have_gdkpixbuf2]) -AC_MSG_NOTICE([Examples: $enable_examples]) -AC_MSG_NOTICE([Tests: $enable_tests (tests will cause 'visibility' to be turned off)]) -AC_MSG_NOTICE([---------------------------------------]) - -AC_CONFIG_FILES([Makefile]) -AC_CONFIG_FILES([examples/Makefile]) -AC_CONFIG_FILES([extra/Makefile]) -AC_CONFIG_FILES([fuzzing/Makefile]) -AC_CONFIG_FILES([gdk-pixbuf/Makefile]) -AC_CONFIG_FILES([go/Makefile]) -AC_CONFIG_FILES([gnome/Makefile]) -AC_CONFIG_FILES([scripts/Makefile]) -AC_CONFIG_FILES([tests/Makefile]) -AC_CONFIG_FILES([libheif/Makefile]) -AC_CONFIG_FILES([libheif/heif_version.h]) -AC_CONFIG_FILES([libheif.pc]) -AC_OUTPUT diff --git a/diagram.svg b/diagram.svg deleted file mode 100644 index 45d365de42..0000000000 --- a/diagram.svg +++ /dev/null @@ -1 +0,0 @@ -catch.hppcatch.hppcatch.hppcpplint.pycpplint.pycpplint.pyrun-ci.shrun-ci.shrun-ci.shheif_plugin.hheif_plugin.hheif_plugin.hheif_image.ccheif_image.ccheif_image.ccheif_file.ccheif_file.ccheif_file.ccheif_encoder_...heif_encoder_...heif_encoder_...heif_encoder_...heif_encoder_...heif_encoder_...heif_encoder_...heif_encoder_...heif_encoder_...heif_emscript...heif_emscript...heif_emscript...heif_decoder_...heif_decoder_...heif_decoder_...heif_cxx.hheif_cxx.hheif_cxx.hheif_context.hheif_context.hheif_context.hheif_context.ccheif_context.ccheif_context.ccheif_colorcon...heif_colorcon...heif_colorcon...heif_avif.ccheif_avif.ccheif_avif.ccheif.hheif.hheif.hheif.ccheif.ccheif.ccbox.hbox.hbox.hbox.ccbox.ccbox.ccbitstream.ccbitstream.ccbitstream.ccheif_hevc.ccheif_hevc.ccheif_hevc.ccdemo.htmldemo.htmldemo.htmlheif_info.ccheif_info.ccheif_info.ccheif_enc.ccheif_enc.ccheif_enc.ccheif_convert.ccheif_convert.ccheif_convert.ccexample.heicexample.heicexample.heicexample.avifexample.avifexample.avifLibFindMacros...LibFindMacros...LibFindMacros...configure.acconfigure.acconfigure.acCOPYINGCOPYINGCOPYINGREADME.mdREADME.mdREADME.mdheif.goheif.goheif.gogithub_20.heicgithub_20.heicgithub_20.heicclusterfuzz-t...clusterfuzz-t...clusterfuzz-t...third-partythird-partyteststestsscriptsscriptsm4m4libheiflibheifgogognomegnomegdk-pixbufgdk-pixbuffuzzingfuzzingextraextraexamplesexamplescmake/modulescmake/modulesheifheifcorpuscorpus.go.html.js.md.py.sh
each dot sized by file size
\ No newline at end of file diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 7365d20c16..fc7f2258ee 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,143 +1,145 @@ # Needed to find libheif/heif_version.h while compiling the library -include_directories(${libheif_BINARY_DIR} ${libheif_SOURCE_DIR}) - -set (heif_convert_sources - encoder.cc - encoder.h - encoder_y4m.cc - encoder_y4m.h - heif_convert.cc -) - -set (additional_link_directories) -set (additional_libraries) -set (additional_includes) +include_directories(${libheif_BINARY_DIR} ${libheif_SOURCE_DIR}/libheif/api ${libheif_SOURCE_DIR}/libheif) + +if (MSVC) + set(getopt_sources + ../extra/getopt.c + ../extra/getopt.h + ../extra/getopt_long.c + ) + include_directories("../extra") +endif () + +add_executable(heif-info ${getopt_sources} + heif_info.cc + common.cc + common.h) +if(WIN32) + if(MSVC) + target_sources(heif-info PRIVATE utf8.manifest) + elseif(MINGW) + target_sources(heif-info PRIVATE utf8.rc) + endif() +endif() +target_link_libraries(heif-info heif) +install(TARGETS heif-info RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +install(FILES heif-info.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) -include (${CMAKE_ROOT}/Modules/FindJPEG.cmake) -if(JPEG_FOUND) -add_definitions(-DHAVE_LIBJPEG=1) -include_directories(SYSTEM ${JPEG_INCLUDE_DIR}) +add_executable(heif-dec ${getopt_sources} + heif_dec.cc + common.cc + common.h) +if(WIN32) + if(MSVC) + target_sources(heif-dec PRIVATE utf8.manifest) + elseif(MINGW) + target_sources(heif-dec PRIVATE utf8.rc) + endif() +endif() +target_link_libraries(heif-dec PRIVATE heif heifio) +target_include_directories(heif-dec PRIVATE ${libheif_SOURCE_DIR}) +install(TARGETS heif-dec RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +install(FILES heif-dec.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) + +# create symbolic link from the old name `heif-convert` to `heif-dec` +if(NOT WIN32) + install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink heif-dec${CMAKE_EXECUTABLE_SUFFIX} \$ENV{DESTDIR}${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/heif-convert${CMAKE_EXECUTABLE_SUFFIX})") +else() + install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/heif-dec${CMAKE_EXECUTABLE_SUFFIX} ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/heif-convert${CMAKE_EXECUTABLE_SUFFIX})") +endif() -include (${CMAKE_ROOT}/Modules/CheckCXXSourceCompiles.cmake) -set(CMAKE_REQUIRED_LIBRARIES ${JPEG_LIBRARIES}) +add_executable(heif-enc ${getopt_sources} + heif_enc.cc + benchmark.h + benchmark.cc + common.cc + common.h + SAI_datafile.cc + SAI_datafile.h) +if(WIN32) + if(MSVC) + target_sources(heif-enc PRIVATE utf8.manifest) + elseif(MINGW) + target_sources(heif-enc PRIVATE utf8.rc) + endif() +endif() +target_link_libraries(heif-enc PRIVATE heif heifio) +target_include_directories(heif-enc PRIVATE ${libheif_SOURCE_DIR}) +install(TARGETS heif-enc RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) +install(FILES heif-enc.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) -# while the docs say JPEG_INCLUDE_DIRS, my FindJPEG.cmake script returns it in JPEG_INCLUDE_DIR -set(CMAKE_REQUIRED_INCLUDES ${JPEG_INCLUDE_DIRS} ${JPEG_INCLUDE_DIR}) +if (WITH_HEADER_COMPRESSION) + target_compile_definitions(heif-enc PRIVATE WITH_HEADER_COMPRESSION=1) +endif () -check_cxx_source_compiles(" -#include -#include -#include +if (ENABLE_EXPERIMENTAL_FEATURES) + target_sources(heif-enc PRIVATE vmt.cc vmt.h) +endif () -int main() { - jpeg_write_icc_profile(NULL, NULL, 0); - return 0; -} -" HAVE_JPEG_WRITE_ICC_PROFILE) -if(HAVE_JPEG_WRITE_ICC_PROFILE) - add_definitions(-DHAVE_JPEG_WRITE_ICC_PROFILE=1) -endif() -set (heif_convert_sources - ${heif_convert_sources} - encoder_jpeg.cc - encoder_jpeg.h -) -set (additional_libraries - ${additional_libraries} - ${JPEG_LIBRARIES} -) -set (additional_includes - ${additional_includes} - ${JPEG_INCLUDE_DIRS} - ${JPEG_INCLUDE_DIR} -) +if (BUILD_DEVELOPMENT_TOOLS AND PNG_FOUND) + add_executable(heif-gen-bayer + heif_gen_bayer.cc) + target_link_libraries(heif-gen-bayer PRIVATE heif heifio) + target_include_directories(heif-gen-bayer PRIVATE ${libheif_SOURCE_DIR}) endif() -if(UNIX OR MINGW) - include (${CMAKE_ROOT}/Modules/FindPkgConfig.cmake) - pkg_check_modules (LIBPNG libpng) - if(LIBPNG_FOUND) - add_definitions(-DHAVE_LIBPNG=1) - set (heif_convert_sources - ${heif_convert_sources} - encoder_png.cc - encoder_png.h - ) - set (additional_link_directories - ${additional_link_directories} - ${LIBPNG_LIBRARY_DIRS} - ) - set (additional_libraries - ${additional_libraries} - ${LIBPNG_LINK_LIBRARIES} ${LIBPNG_LIBRARIES} - ) - set (additional_includes - ${additional_includes} - ${LIBPNG_INCLUDE_DIRS} - ) - endif() -endif() -set (heif_info_sources - heif_info.cc -) - -set (heif_enc_sources - heif_enc.cc -) - -set (heif_test_sources - heif_test.cc -) - -if(MSVC) - set (getopt_sources - ../extra/getopt.c - ../extra/getopt.h - ../extra/getopt_long.c - ) - include_directories ("../extra") +if (WITH_EXAMPLE_HEIF_THUMB AND PNG_FOUND) + add_executable(heif-thumbnailer ${getopt_sources} + heif_thumbnailer.cc + common.cc + common.h) + if(WIN32) + if(MSVC) + target_sources(heif-thumbnailer PRIVATE utf8.manifest) + elseif(MINGW) + target_sources(heif-thumbnailer PRIVATE utf8.rc) + endif() + endif() + target_link_libraries(heif-thumbnailer heif heifio) + target_include_directories(heif-thumbnailer PRIVATE ${libheif_SOURCE_DIR}) + + install(TARGETS heif-thumbnailer RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + install(FILES heif-thumbnailer.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) endif() -add_executable (heif-convert ${heif_convert_sources} ${getopt_sources}) -target_link_directories (heif-convert PRIVATE ${additional_link_directories}) -target_link_libraries (heif-convert heif ${additional_libraries}) -target_include_directories(heif-convert PRIVATE ${additional_includes}) -install(TARGETS heif-convert RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) -install(FILES heif-convert.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) - -add_executable (heif-info ${heif_info_sources} ${getopt_sources}) -target_link_libraries (heif-info heif) -install(TARGETS heif-info RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) -install(FILES heif-info.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) -add_executable (heif-enc ${heif_enc_sources} ${getopt_sources}) -target_link_directories (heif-enc PRIVATE ${additional_link_directories}) -target_link_libraries (heif-enc heif ${additional_libraries}) -target_include_directories(heif-enc PRIVATE ${additional_includes}) -install(TARGETS heif-enc RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) -install(FILES heif-enc.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) -add_executable (heif-test ${heif_test_sources} ${getopt_sources}) -target_link_libraries (heif-test heif) - - -if(LIBPNG_FOUND) - set (heif_thumbnailer_sources - encoder.cc - encoder.h - heif_thumbnailer.cc - encoder_png.cc - encoder_png.h - ) - - add_executable (heif-thumbnailer ${heif_thumbnailer_sources}) - target_link_directories (heif-thumbnailer PRIVATE ${LIBPNG_LIBRARY_DIRS}) - target_link_libraries (heif-thumbnailer heif ${LIBPNG_LINK_LIBRARIES} ${LIBPNG_LIBRARIES}) - target_include_directories(heif-thumbnailer PRIVATE ${LIBPNG_INCLUDE_DIRS}) - install(TARGETS heif-thumbnailer RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) - install(FILES heif-thumbnailer.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) +if (WITH_EXAMPLE_HEIF_VIEW) + find_package(SDL2 NO_MODULE) + + if (SDL2_FOUND) + add_executable(heif-view ${getopt_sources} + heif_view.cc + sdl.cc + sdl.hh + common.cc + common.h) + if(WIN32) + if(MSVC) + target_sources(heif-view PRIVATE utf8.manifest) + elseif(MINGW) + target_sources(heif-view PRIVATE utf8.rc) + endif() + endif() + target_link_libraries(heif-view PRIVATE heif SDL2::SDL2main SDL2::SDL2) + install(TARGETS heif-view RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) + endif () +endif () + + +add_executable(heif-test ${getopt_sources} + heif_test.cc + common.cc + common.h) +if(WIN32) + if(MSVC) + target_sources(heif-test PRIVATE utf8.manifest) + elseif(MINGW) + target_sources(heif-test PRIVATE utf8.rc) + endif() endif() +target_link_libraries(heif-test heif) diff --git a/examples/Makefile.am b/examples/Makefile.am deleted file mode 100644 index bb2909fbec..0000000000 --- a/examples/Makefile.am +++ /dev/null @@ -1,112 +0,0 @@ -AUTOMAKE_OPTIONS = subdir-objects - -examples = \ - heif-convert \ - heif-enc \ - heif-info - -examples_noinst = \ - heif-test \ - test-c-api - -dist_man_MANS = - -heif_convert_DEPENDENCIES = ../libheif/libheif.la -heif_convert_CXXFLAGS = -I$(top_srcdir) -I$(top_builddir)/. -heif_convert_LDFLAGS = -heif_convert_LDADD = ../libheif/libheif.la -heif_convert_SOURCES = encoder.cc encoder.h heif_convert.cc encoder_y4m.cc encoder_y4m.h -dist_man_MANS += heif-convert.1 - - -if HAVE_LIBPNG -examples += heif-thumbnailer -heif_thumbnailer_DEPENDENCIES = ../libheif/libheif.la -heif_thumbnailer_CXXFLAGS = -I$(top_srcdir) -I$(top_builddir) $(libpng_CFLAGS) -heif_thumbnailer_LDFLAGS = $(libpng_LIBS) -heif_thumbnailer_LDADD = ../libheif/libheif.la -heif_thumbnailer_SOURCES = encoder.cc encoder.h heif_thumbnailer.cc encoder_png.cc encoder_png.h -dist_man_MANS += heif-thumbnailer.1 -endif - -if HAVE_LIBJPEG -heif_convert_CXXFLAGS += $(libjpeg_CFLAGS) -heif_convert_LDADD += $(libjpeg_LIBS) -heif_convert_SOURCES += encoder_jpeg.cc encoder_jpeg.h -endif - -if HAVE_LIBPNG -heif_convert_CXXFLAGS += $(libpng_CFLAGS) -heif_convert_LDADD += $(libpng_LIBS) -heif_convert_SOURCES += encoder_png.cc encoder_png.h -endif - -heif_info_DEPENDENCIES = ../libheif/libheif.la -heif_info_CXXFLAGS = -I$(top_srcdir) -I$(top_builddir) -heif_info_LDFLAGS = -heif_info_LDADD = ../libheif/libheif.la -heif_info_SOURCES = heif_info.cc -dist_man_MANS += heif-info.1 - -heif_enc_DEPENDENCIES = ../libheif/libheif.la -heif_enc_CXXFLAGS = -I$(top_srcdir) -I$(top_builddir) -heif_enc_LDFLAGS = -heif_enc_LDADD = ../libheif/libheif.la -heif_enc_SOURCES = heif_enc.cc -dist_man_MANS += heif-enc.1 - -if HAVE_LIBJPEG -heif_enc_CXXFLAGS += $(libjpeg_CFLAGS) -heif_enc_LDADD += $(libjpeg_LIBS) -endif - -if HAVE_LIBPNG -heif_enc_CXXFLAGS += $(libpng_CFLAGS) -heif_enc_LDADD += $(libpng_LIBS) -endif - -heif_test_DEPENDENCIES = ../libheif/libheif.la -heif_test_CXXFLAGS = -I$(top_srcdir) -I$(top_builddir) -heif_test_LDFLAGS = -heif_test_LDADD = ../libheif/libheif.la -heif_test_SOURCES = heif_test.cc - -test_c_api_DEPENDENCIES = ../libheif/libheif.la -test_c_api_CFLAGS = -I$(top_srcdir) -I$(top_builddir) -test_c_api_LDFLAGS = -test_c_api_LDADD = ../libheif/libheif.la -test_c_api_SOURCES = test_c_api.c - -EXTRA_DIST = \ - CMakeLists.txt \ - COPYING \ - demo.html \ - example.avif \ - example.heic - -if HAVE_GO -examples_noinst += \ - heif-test-go - -heif_test_go_SOURCES = heif-test.go - -gopath: - mkdir -p ${CURDIR}/src/github.com/strukturag/libheif - ln -sf ${CURDIR}/../go ${CURDIR}/src/github.com/strukturag/libheif/ - -heif-test-go: gopath $(top_builddir)/libheif/libheif.la $(top_builddir)/libheif.pc heif-test.go - GOPATH=${CURDIR} PKG_CONFIG_PATH=${PKG_CONFIG_PATH}:$(abs_top_builddir):$(abs_top_builddir)/libde265/dist/lib/pkgconfig/ CGO_CFLAGS="-I$(abs_top_builddir)" CGO_LDFLAGS="-L$(abs_top_builddir)/libheif/.libs" LD_LIBRARY_PATH=$(abs_top_builddir)/libheif/.libs $(GO) build -o heif-test-go ${heif_test_go_SOURCES} - -format-go: ${heif_test_go_SOURCES} - $(GO) fmt ${heif_test_go_SOURCES} -else -format-go: - echo ""go" not present in "${PATH}", skipping formatting" -endif - -if WITH_EXAMPLES -bin_PROGRAMS = $(examples) -noinst_PROGRAMS = $(examples_noinst) -endif - -format-local: format-go diff --git a/examples/SAI_datafile.cc b/examples/SAI_datafile.cc new file mode 100644 index 0000000000..072225aadb --- /dev/null +++ b/examples/SAI_datafile.cc @@ -0,0 +1,238 @@ +/* + * HEIF codec. + * Copyright (c) 2025 Dirk Farin + * + * This file is part of libheif. + * + * libheif is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * libheif is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libheif. If not, see . + */ + +#include "SAI_datafile.h" + +#include +#include +#include +#include + + +SAI_datafile::~SAI_datafile() +{ + heif_tai_clock_info_release(tai_clock_info); + + for (auto tai : tai_timestamps) { + heif_tai_timestamp_packet_release(tai); + } +} + + +void SAI_datafile::handleHeaderEntry(const std::string& code, + const std::vector& values) +{ + if (code == "suid") { + active_sais.push_back(code); + if (!values.empty()) { + std::cerr << "Invalid 'suid' header line. May not have additional parameters.\n"; + exit(5); + } + } + else if (code == "stai") { + active_sais.push_back(code); + if (values.size() > 4) { + std::cerr << "Invalid 'stai' header line. May not have more than 4 parameters.\n"; + exit(5); + } + + tai_clock_info = heif_tai_clock_info_alloc(); + + for (size_t i=0;i 0xffffffff)) { + std::cerr << "Invalid SAI tai clock info entry in header\n"; + exit(5); + } + + if (i==2 && (val < 0 || val > 0x7fffffff)) { + std::cerr << "Invalid SAI tai clock info entry in header\n"; + exit(5); + } + + if (i==3 && (val < 0 || val > 0xff)) { + std::cerr << "Invalid SAI tai clock info entry in header\n"; + exit(5); + } + + switch (i) { + case 0: + tai_clock_info->time_uncertainty = val; + break; + case 1: + tai_clock_info->clock_resolution = static_cast(val); + break; + case 2: + tai_clock_info->clock_drift_rate = static_cast(val); + break; + case 3: + tai_clock_info->clock_type = static_cast(val); + break; + } + } + } + else { + std::cerr << "Unknown code in SAI data file header: " << code << "\n"; + exit(5); + } +} + +void SAI_datafile::handleMainEntry(const std::vector& values, int line, int main_item_line) +{ + if (active_sais.empty()) { + std::cerr << "Invalid SAI data file: data received, but no SAIs defined."; + exit(5); + } + + size_t idx = main_item_line % active_sais.size(); + if (active_sais[idx] == "suid") { + if (values.size() > 1) { + std::cerr << "Invalid SAI content-id entry in line " << line << "\n"; + exit(5); + } + + if (values.empty()) { + gimi_content_ids.push_back({}); + } + else { + gimi_content_ids.push_back(values[0]); + } + } + else if (active_sais[idx] == "stai") { + if (values.size() > 4) { + std::cerr << "Invalid SAI timestamp entry in line " << line << "\n"; + exit(5); + } + + if (values.empty()) { + tai_timestamps.push_back(nullptr); + } + else { + heif_tai_timestamp_packet* tai = heif_tai_timestamp_packet_alloc(); + for (size_t i=0;i=1 && i<=3 && (val < 0 || val > 1)) { + std::cerr << "Invalid SAI timestamp entry in line " << line << "\n"; + exit(5); + } + + switch (i) { + case 0: + tai->tai_timestamp = val; + break; + case 1: + tai->synchronization_state = static_cast(val); + break; + case 2: + tai->timestamp_generation_failure = static_cast(val); + break; + case 3: + tai->timestamp_is_modified = static_cast(val); + break; + } + } + + tai_timestamps.push_back(tai); + } + } +} + +bool SAI_datafile::isSeparatorLine(const std::string& line) +{ + return line.starts_with("---"); +} + +std::vector SAI_datafile::splitCSV(const std::string& line) +{ + std::vector parts; + std::stringstream ss(line); + std::string item; + + while (std::getline(ss, item, ',')) { + // Trim whitespace + item.erase(item.begin(), + std::find_if(item.begin(), item.end(), [](unsigned char ch){ return !std::isspace(ch); })); + item.erase( + std::find_if(item.rbegin(), item.rend(), [](unsigned char ch){ return !std::isspace(ch); }).base(), + item.end() + ); + parts.push_back(item); + } + + return parts; +} + + +void SAI_datafile::load_sai_data_from_file(const char* sai_file) +{ + std::ifstream istr(sai_file); + if (!istr) { + std::cerr << "Could not open SAI data file\n"; + exit(5); + } + + // --- read header + + std::string line; + bool inHeader = true; + int line_counter = 0; + int main_item_line = 0; + + while (std::getline(istr, line)) { + line_counter++; + + if (inHeader && line.empty()) + continue; + + if (inHeader && isSeparatorLine(line)) { + // Switch to main part + inHeader = false; + continue; + } + + if (inHeader) { + // Header line: starts with 4-character code, optional CSV list + if (line.size() < 4) { + std::cerr << "Invalid header line: " << line << "\n"; + continue; + } + + std::string code = line.substr(0, 4); + + std::vector values; + if (line.size() > 4) { + // Skip the space after the code, if present + std::string rest = line.substr(4); + if (!rest.empty() && (rest[0] == ' ' || rest[0] == '\t')) + rest.erase(0, 1); + + if (!rest.empty()) + values = splitCSV(rest); + } + + handleHeaderEntry(code, values); + } + else { + // Main section: entire line is CSV + auto values = splitCSV(line); + handleMainEntry(values, line_counter, main_item_line++); + } + } +} diff --git a/examples/SAI_datafile.h b/examples/SAI_datafile.h new file mode 100644 index 0000000000..bb95c3a825 --- /dev/null +++ b/examples/SAI_datafile.h @@ -0,0 +1,54 @@ +/* + * HEIF codec. + * Copyright (c) 2025 Dirk Farin + * + * This file is part of libheif. + * + * libheif is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * libheif is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libheif. If not, see . + */ + +#ifndef SAI_DATAFILE_H +#define SAI_DATAFILE_H +#include +#include + +#include "libheif/heif_tai_timestamps.h" + +// Helper class for heif-enc to read a SAI data file and provide the data for the track's SAI items. +struct SAI_datafile +{ + ~SAI_datafile(); + + void load_sai_data_from_file(const char* sai_file); + + heif_tai_clock_info* tai_clock_info = nullptr; + + std::vector tai_timestamps; + std::vector gimi_content_ids; + + std::vector active_sais; + +private: + void handleHeaderEntry(const std::string& code, + const std::vector& values); + + void handleMainEntry(const std::vector& values, int line, int main_item_line); + + bool isSeparatorLine(const std::string& line); + + std::vector splitCSV(const std::string& line); +}; + + +#endif //SAI_DATAFILE_H diff --git a/examples/benchmark.cc b/examples/benchmark.cc new file mode 100644 index 0000000000..7694584485 --- /dev/null +++ b/examples/benchmark.cc @@ -0,0 +1,99 @@ +/* + * HEIF codec. + * Copyright (c) 2022 Dirk Farin + * + * This file is part of libheif. + * + * libheif is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * libheif is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libheif. If not, see . + */ + +#include "benchmark.h" +#include "libheif/heif.h" +#include + + +double compute_psnr(heif_image* original_image, const std::string& encoded_file) +{ + double psnr = 0.0; + + // read encoded image + + struct heif_context* ctx = nullptr; + struct heif_image_handle* handle = nullptr; + struct heif_image* image = nullptr; + heif_error err{}; + + int orig_stride = 0; + const uint8_t* orig_p = nullptr; + int compressed_stride = 0; + const uint8_t* compressed_p = nullptr; + + int w = 0, h = 0; + double mse = 0.0; + + + if (heif_image_get_colorspace(original_image) != heif_colorspace_YCbCr && + heif_image_get_colorspace(original_image) != heif_colorspace_monochrome) { + fprintf(stderr, "Benchmark can only be computed on YCbCr or monochrome images\n"); + goto cleanup; + } + + + ctx = heif_context_alloc(); + + err = heif_context_read_from_file(ctx, encoded_file.c_str(), nullptr); + if (err.code) { + fprintf(stderr, "Error reading encoded file: %s\n", err.message); + goto cleanup; + } + + err = heif_context_get_primary_image_handle(ctx, &handle); + if (err.code) { + fprintf(stderr, "Error getting primary image handle: %s\n", err.message); + goto cleanup; + } + + err = heif_decode_image(handle, &image, + heif_image_get_colorspace(original_image), + heif_image_get_chroma_format(original_image), + nullptr); + if (err.code) { + fprintf(stderr, "Error decoding image: %s\n", err.message); + goto cleanup; + } + + w = heif_image_get_width(original_image, heif_channel_Y); + h = heif_image_get_height(original_image, heif_channel_Y); + + orig_p = heif_image_get_plane_readonly(original_image, heif_channel_Y, &orig_stride); + compressed_p = heif_image_get_plane_readonly(image, heif_channel_Y, &compressed_stride); + + for (int y = 0; y < h; y++) { + for (int x = 0; x < w; x++) { + int d = orig_p[y * orig_stride + x] - compressed_p[y * compressed_stride + x]; + mse += d * d; + } + } + + mse /= w * h; + + psnr = 10 * log10(255.0 * 255.0 / mse); + + cleanup: + heif_image_release(image); + heif_image_handle_release(handle); + heif_context_free(ctx); + + return psnr; +} diff --git a/examples/benchmark.h b/examples/benchmark.h new file mode 100644 index 0000000000..a214efb30b --- /dev/null +++ b/examples/benchmark.h @@ -0,0 +1,32 @@ +/* + * HEIF codec. + * Copyright (c) 2022 Dirk Farin + * + * This file is part of libheif. + * + * libheif is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * libheif is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libheif. If not, see . + */ + + +#ifndef LIBHEIF_BENCHMARK_H +#define LIBHEIF_BENCHMARK_H + +#include +#include + +struct heif_image; + +double compute_psnr(heif_image* original_image, const std::string& encoded_file); + +#endif //LIBHEIF_BENCHMARK_H diff --git a/examples/common.cc b/examples/common.cc new file mode 100644 index 0000000000..04408a1fa1 --- /dev/null +++ b/examples/common.cc @@ -0,0 +1,151 @@ +/* + libheif example application "heif". + + MIT License + + Copyright (c) 2023 Dirk Farin + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#include "common.h" +#include "libheif/heif.h" +#include +#include +#include +#include +#include + +#include "common_utils.h" + +namespace heif_examples { + void show_version() + { + std::cout << LIBHEIF_VERSION << '\n' + << "libheif: " << heif_get_version() << '\n'; + { + auto paths = heif_get_plugin_directories(); + for (int i = 0; paths[i]; i++) { + std::cout << "plugin path: " << paths[i] << '\n'; + } + + if (paths[0] == nullptr) { + std::cout << "plugin path: plugins are disabled\n"; + } + + heif_free_plugin_directories(paths); + } + } + + +#define MAX_DECODERS 20 + + void list_decoders(heif_compression_format format) + { + const heif_decoder_descriptor* decoders[MAX_DECODERS]; + int n = heif_get_decoder_descriptors(format, decoders, MAX_DECODERS); + + for (int i = 0; i < n; i++) { + const char* id = heif_decoder_descriptor_get_id_name(decoders[i]); + if (id == nullptr) { + id = "---"; + } + + std::cout << "- " << id << " = " << heif_decoder_descriptor_get_name(decoders[i]) << "\n"; + } + } + + + void list_all_decoders() + { + std::cout << "AVC decoders:\n"; + list_decoders(heif_compression_AVC); + + std::cout << "AVIF decoders:\n"; + list_decoders(heif_compression_AV1); + + std::cout << "HEIC decoders:\n"; + list_decoders(heif_compression_HEVC); + + std::cout << "JPEG decoders:\n"; + list_decoders(heif_compression_JPEG); + + std::cout << "JPEG 2000 decoders:\n"; + list_decoders(heif_compression_JPEG2000); + + std::cout << "JPEG 2000 (HT) decoders:\n"; + list_decoders(heif_compression_HTJ2K); + + std::cout << "uncompressed:\n"; + list_decoders(heif_compression_uncompressed); + + std::cout << "VVIC decoders:\n"; + list_decoders(heif_compression_VVC); + } + + + std::string fourcc_to_string(uint32_t fourcc) + { + char s[5]; + s[0] = static_cast((fourcc >> 24) & 0xFF); + s[1] = static_cast((fourcc >> 16) & 0xFF); + s[2] = static_cast((fourcc >> 8) & 0xFF); + s[3] = static_cast((fourcc) & 0xFF); + s[4] = 0; + + return s; + } + + + int check_for_valid_input_HEIF_file(const std::string& input_filename) + { + std::ifstream istr(input_filename.c_str(), std::ios_base::binary); + if (istr.fail()) { + fprintf(stderr, "Input file does not exist.\n"); + return 10; + } + + std::array length{}; + istr.read((char*) length.data(), length.size()); + uint32_t box_size = four_bytes_to_uint32(length[0], length[1], length[2], length[3]); + if ((box_size < 16) || (box_size > 512)) { + fprintf(stderr, "Input file does not appear to start with a valid box length."); + if ((box_size & 0xFFFFFFF0) == 0xFFD8FFE0) { + fprintf(stderr, " Possibly could be a JPEG file instead.\n"); + } + else { + fprintf(stderr, "\n"); + } + return 1; + } + + std::vector ftyp_bytes(box_size); + std::copy(length.begin(), length.end(), ftyp_bytes.begin()); + istr.read((char*) ftyp_bytes.data() + 4, ftyp_bytes.size() - 4); + + heif_error filetype_check = heif_has_compatible_filetype(ftyp_bytes.data(), (int) ftyp_bytes.size()); + if (filetype_check.code != heif_error_Ok) { + fprintf(stderr, "Input file is not a supported format. %s\n", filetype_check.message); + return 1; + } + + return 0; + } + +} diff --git a/examples/common.h b/examples/common.h new file mode 100644 index 0000000000..cdf721492f --- /dev/null +++ b/examples/common.h @@ -0,0 +1,47 @@ +/* + libheif example application "heif". + + MIT License + + Copyright (c) 2023 Dirk Farin + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#ifndef LIBHEIF_COMMON_H +#define LIBHEIF_COMMON_H + +#include +#include + +namespace heif_examples { +// Note: the same function is also exists in common_utils.h, but is not in the public API. + std::string fourcc_to_string(uint32_t fourcc); + + void show_version(); + + void list_all_decoders(); + + void list_decoders(heif_compression_format format); + +// returns 0 on success, or program exit code in case of warning/error + int check_for_valid_input_HEIF_file(const std::string& input_filename); +} + +#endif //LIBHEIF_COMMON_H diff --git a/examples/demo.html b/examples/demo.html index beb200346b..1c6f8275a7 100644 --- a/examples/demo.html +++ b/examples/demo.html @@ -191,6 +191,8 @@

libheif decoder demo

}.bind(this)); this.drawer = new CanvasDrawer(canvas); this.decoder = new libheif.HeifDecoder(); + + console.log("Using libheif", libheif.heif_get_version()); saveSupported = this.canvas.toBlob && ((URL && URL.createObjectURL) || navigator.msSaveOrOpenBlob); if (saveSupported) { @@ -313,8 +315,7 @@

libheif decoder demo

return; } - console.log("Using libheif", libheif.heif_get_version()); - var demo = new HeifDemo(libheif); + var demo = new HeifDemo(libheif()); show("form"); diff --git a/examples/encoder_png.cc b/examples/encoder_png.cc deleted file mode 100644 index d5158dbf4a..0000000000 --- a/examples/encoder_png.cc +++ /dev/null @@ -1,142 +0,0 @@ -/* - libheif example application "convert". - - MIT License - - Copyright (c) 2017 struktur AG, Joachim Bauch - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. -*/ -#include -#include -#include -#include -#include -#include - -#include "encoder_png.h" - -PngEncoder::PngEncoder() -{} - - -bool PngEncoder::Encode(const struct heif_image_handle* handle, - const struct heif_image* image, const std::string& filename) -{ - png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, - nullptr, nullptr); - if (!png_ptr) { - fprintf(stderr, "libpng initialization failed (1)\n"); - return false; - } - - png_infop info_ptr = png_create_info_struct(png_ptr); - if (!info_ptr) { - png_destroy_write_struct(&png_ptr, nullptr); - fprintf(stderr, "libpng initialization failed (2)\n"); - return false; - } - - FILE* fp = fopen(filename.c_str(), "wb"); - if (!fp) { - fprintf(stderr, "Can't open %s: %s\n", filename.c_str(), strerror(errno)); - png_destroy_write_struct(&png_ptr, &info_ptr); - return false; - } - - if (setjmp(png_jmpbuf(png_ptr))) { - png_destroy_write_struct(&png_ptr, &info_ptr); - fclose(fp); - fprintf(stderr, "Error while encoding image\n"); - return false; - } - - png_init_io(png_ptr, fp); - - bool withAlpha = (heif_image_get_chroma_format(image) == heif_chroma_interleaved_RGBA || - heif_image_get_chroma_format(image) == heif_chroma_interleaved_RRGGBBAA_BE); - - int width = heif_image_get_width(image, heif_channel_interleaved); - int height = heif_image_get_height(image, heif_channel_interleaved); - - int bitDepth; - int input_bpp = heif_image_get_bits_per_pixel_range(image, heif_channel_interleaved); - if (input_bpp > 8) { - bitDepth = 16; - } - else { - bitDepth = 8; - } - - const int colorType = withAlpha ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_RGB; - - png_set_IHDR(png_ptr, info_ptr, width, height, bitDepth, colorType, - PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); - - size_t profile_size = heif_image_handle_get_raw_color_profile_size(handle); - if (profile_size > 0) { - uint8_t* profile_data = static_cast(malloc(profile_size)); - heif_image_handle_get_raw_color_profile(handle, profile_data); - char profile_name[] = "unknown"; - png_set_iCCP(png_ptr, info_ptr, profile_name, PNG_COMPRESSION_TYPE_BASE, -#if PNG_LIBPNG_VER < 10500 - (png_charp)profile_data, -#else - (png_const_bytep) profile_data, -#endif - (png_uint_32) profile_size); - free(profile_data); - } - png_write_info(png_ptr, info_ptr); - - uint8_t** row_pointers = new uint8_t* [height]; - - int stride_rgb; - const uint8_t* row_rgb = heif_image_get_plane_readonly(image, - heif_channel_interleaved, &stride_rgb); - - for (int y = 0; y < height; ++y) { - row_pointers[y] = const_cast(&row_rgb[y * stride_rgb]); - } - - if (bitDepth == 16) { - // shift image data to full 16bit range - - int shift = 16 - input_bpp; - if (shift > 0) { - for (int y = 0; y < height; ++y) { - for (int x = 0; x < stride_rgb; x += 2) { - uint8_t* p = (&row_pointers[y][x]); - int v = (p[0] << 8) | p[1]; - v = (v << shift) | (v >> (16 - shift)); - p[0] = (uint8_t) (v >> 8); - p[1] = (uint8_t) (v & 0xFF); - } - } - } - } - - - png_write_image(png_ptr, row_pointers); - png_write_end(png_ptr, nullptr); - png_destroy_write_struct(&png_ptr, &info_ptr); - delete[] row_pointers; - fclose(fp); - return true; -} diff --git a/examples/example.heic b/examples/example.heic index 91d5dc1497..8293840378 100644 Binary files a/examples/example.heic and b/examples/example.heic differ diff --git a/examples/heif-convert.1 b/examples/heif-dec.1 similarity index 88% rename from examples/heif-convert.1 rename to examples/heif-dec.1 index 171acbbc78..da55eae503 100644 --- a/examples/heif-convert.1 +++ b/examples/heif-dec.1 @@ -1,18 +1,18 @@ -.TH HEIF-THUMBNAILER 1 +.TH HEIF-CONVERT 1 .SH NAME -heif-convert \- convert HEIC/HEIF image +heif-dec \- decode HEIC/HEIF image .SH SYNOPSIS -.B heif-convert +.B heif-dec [\fB\-q\fR \fIQUALITY\fR] .IR filename .IR output[.jpg|.png|.y4m] .SH DESCRIPTION -.B heif-convert +.B heif-dec Convert HEIC/HEIF image to a different image format. .SH OPTIONS .TP .BR \-q\fR\ \fIQUALITY\fR -Defines quality level between 0 and 100 for the generated output file. +Defines quality level between 0 and 100 for the generated output file. Only used for JPEG. .SH EXIT STATUS .PP \fB0\fR diff --git a/examples/heif-enc.1 b/examples/heif-enc.1 index 22164cdaf8..b7ee50bd2b 100644 --- a/examples/heif-enc.1 +++ b/examples/heif-enc.1 @@ -38,11 +38,11 @@ Do not save alpha channel in thumbnail image. .BR \-o\fR\ \fIFILENAME\fR ", " \-\-output\fR\ \fIFILENAME\fR Output filename (optional). .TP -.BR \-v ", "\-\-verbose\fR -Enable logging output (more \fB\-v\fR will increase logging level). +.BR \-\-verbose\fR +Enable logging output (more will increase logging level). .TP .BR \-P ", "\-\-params\fR -Show all encoder parameters. +Show all encoder parameters and exit. Input file is not required or used. .TP .BR \-b\fR\ \fIDEPTH\fR Bit-depth of generated HEIF file when using 16-bit PNG input (default: 10 bit). diff --git a/examples/heif-info.1 b/examples/heif-info.1 index 00dc149a53..67ef284023 100644 --- a/examples/heif-info.1 +++ b/examples/heif-info.1 @@ -5,6 +5,7 @@ heif-info \- show information on HEIC/HEIF file .B heif-info [\fB\-d\fR|\fB--dump-boxes\fR] [\fB\-h\fR|\fB--help\fR] +[\fB\-v\fR|\fB--version\fR] .IR filename .SH DESCRIPTION .B heif-info @@ -14,8 +15,11 @@ Show information on HEIC/HEIF file. .BR \-d ", " \-\-dump-boxes\fR Show a low-level dump of all MP4 file boxes. .TP -.BR \-help ", " \-\-help\fR -Show help. +.BR \-h ", " \-\-help\fR +Show help. A filename is not required or used. +.TP +.BR \-v ", " \-\-version\fR +Show version information for the tool, library version, and the plugin path. A filename is not required or used. .SH EXIT STATUS .PP \fB0\fR diff --git a/examples/heif-test.go b/examples/heif-test.go index 7540394abb..aed6c6c0ea 100644 --- a/examples/heif-test.go +++ b/examples/heif-test.go @@ -3,7 +3,7 @@ MIT License - Copyright (c) 2018 struktur AG, Dirk Farin + Copyright (c) 2018 Dirk Farin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/examples/heif_convert.cc b/examples/heif_convert.cc deleted file mode 100644 index d878a97112..0000000000 --- a/examples/heif_convert.cc +++ /dev/null @@ -1,388 +0,0 @@ -/* - libheif example application "convert". - - MIT License - - Copyright (c) 2017 struktur AG, Joachim Bauch - - Permission is hereby granted, free of charge, to any person obtaining a copy - of this software and associated documentation files (the "Software"), to deal - in the Software without restriction, including without limitation the rights - to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - copies of the Software, and to permit persons to whom the Software is - furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included in all - copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - SOFTWARE. -*/ -#if defined(HAVE_CONFIG_H) -#include "config.h" -#endif - -#include - -#if defined(HAVE_UNISTD_H) - -#include - -#endif - -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "encoder.h" - -#if HAVE_LIBJPEG - -#include "encoder_jpeg.h" - -#endif -#if HAVE_LIBPNG - -#include "encoder_png.h" - -#endif - -#include "encoder_y4m.h" - -#if defined(__MINGW32__) || defined(__MINGW64__) || defined(_MSC_VER) -#include "getopt.h" -#endif - -#define UNUSED(x) (void)x - -static int usage(const char* command) -{ - fprintf(stderr, "USAGE: %s [-q quality 0..100] \n", command); - return 1; -} - -class ContextReleaser -{ -public: - ContextReleaser(struct heif_context* ctx) : ctx_(ctx) - {} - - ~ContextReleaser() - { - heif_context_free(ctx_); - } - -private: - struct heif_context* ctx_; -}; - -int main(int argc, char** argv) -{ - int opt; - int quality = -1; // Use default quality. - UNUSED(quality); // The quality will only be used by encoders that support it. - while ((opt = getopt(argc, argv, "q:")) != -1) { - switch (opt) { - case 'q': - quality = atoi(optarg); - break; - default: /* '?' */ - return usage(argv[0]); - } - } - - if (optind + 2 > argc) { - // Need input and output filenames as additional arguments. - return usage(argv[0]); - } - - std::string input_filename(argv[optind++]); - std::string output_filename(argv[optind++]); - - std::unique_ptr encoder; - - size_t dot_pos = output_filename.rfind('.'); - if (dot_pos != std::string::npos) { - std::string suffix_lowercase = output_filename.substr(dot_pos + 1); - - std::transform(suffix_lowercase.begin(), suffix_lowercase.end(), - suffix_lowercase.begin(), ::tolower); - - if (suffix_lowercase == "jpg" || suffix_lowercase == "jpeg") { -#if HAVE_LIBJPEG - static const int kDefaultJpegQuality = 90; - if (quality == -1) { - quality = kDefaultJpegQuality; - } - encoder.reset(new JpegEncoder(quality)); -#else - fprintf(stderr, "JPEG support has not been compiled in.\n"); - return 1; -#endif // HAVE_LIBJPEG - } - - if (suffix_lowercase == "png") { -#if HAVE_LIBPNG - encoder.reset(new PngEncoder()); -#else - fprintf(stderr, "PNG support has not been compiled in.\n"); - return 1; -#endif // HAVE_LIBPNG - } - - if (suffix_lowercase == "y4m") { - encoder.reset(new Y4MEncoder()); - } - } - - if (!encoder) { - fprintf(stderr, "Unknown file type in %s\n", output_filename.c_str()); - return 1; - } - - - // --- check whether input is a supported HEIF file - - // TODO: when we are reading from named pipes, we probably should not consume any bytes - // just for file-type checking. - // TODO: check, whether reading from named pipes works at all. - - std::ifstream istr(input_filename.c_str(), std::ios_base::binary); - uint8_t magic[12]; - istr.read((char*) magic, 12); - enum heif_filetype_result filetype_check = heif_check_filetype(magic, 12); - if (filetype_check == heif_filetype_no) { - fprintf(stderr, "Input file is not an HEIF/AVIF file\n"); - return 1; - } - - if (filetype_check == heif_filetype_yes_unsupported) { - fprintf(stderr, "Input file is an unsupported HEIF/AVIF file type\n"); - return 1; - } - - - - // --- read the HEIF file - - struct heif_context* ctx = heif_context_alloc(); - if (!ctx) { - fprintf(stderr, "Could not create context object\n"); - return 1; - } - - ContextReleaser cr(ctx); - struct heif_error err; - err = heif_context_read_from_file(ctx, input_filename.c_str(), nullptr); - if (err.code != 0) { - std::cerr << "Could not read HEIF/AVIF file: " << err.message << "\n"; - return 1; - } - - int num_images = heif_context_get_number_of_top_level_images(ctx); - if (num_images == 0) { - fprintf(stderr, "File doesn't contain any images\n"); - return 1; - } - - printf("File contains %d images\n", num_images); - - std::vector image_IDs(num_images); - num_images = heif_context_get_list_of_top_level_image_IDs(ctx, image_IDs.data(), num_images); - - - std::string filename; - size_t image_index = 1; // Image filenames are "1" based. - - for (int idx = 0; idx < num_images; ++idx) { - - if (num_images > 1) { - std::ostringstream s; - s << output_filename.substr(0, output_filename.find_last_of('.')); - s << "-" << image_index; - s << output_filename.substr(output_filename.find_last_of('.')); - filename.assign(s.str()); - } - else { - filename.assign(output_filename); - } - - struct heif_image_handle* handle; - err = heif_context_get_image_handle(ctx, image_IDs[idx], &handle); - if (err.code) { - std::cerr << "Could not read HEIF/AVIF image " << idx << ": " - << err.message << "\n"; - return 1; - } - - int has_alpha = heif_image_handle_has_alpha_channel(handle); - struct heif_decoding_options* decode_options = heif_decoding_options_alloc(); - encoder->UpdateDecodingOptions(handle, decode_options); - - int bit_depth = heif_image_handle_get_luma_bits_per_pixel(handle); - if (bit_depth < 0) { - heif_decoding_options_free(decode_options); - heif_image_handle_release(handle); - std::cerr << "Input image has undefined bit-depth\n"; - return 1; - } - - struct heif_image* image; - err = heif_decode_image(handle, - &image, - encoder->colorspace(has_alpha), - encoder->chroma(has_alpha, bit_depth), - decode_options); - heif_decoding_options_free(decode_options); - if (err.code) { - heif_image_handle_release(handle); - std::cerr << "Could not decode image: " << idx << ": " - << err.message << "\n"; - return 1; - } - - if (image) { - bool written = encoder->Encode(handle, image, filename); - if (!written) { - fprintf(stderr, "could not write image\n"); - } - else { - printf("Written to %s\n", filename.c_str()); - } - heif_image_release(image); - - - int has_depth = heif_image_handle_has_depth_image(handle); - if (has_depth) { - heif_item_id depth_id; - int nDepthImages = heif_image_handle_get_list_of_depth_image_IDs(handle, &depth_id, 1); - assert(nDepthImages == 1); - (void) nDepthImages; - - struct heif_image_handle* depth_handle; - err = heif_image_handle_get_depth_image_handle(handle, depth_id, &depth_handle); - if (err.code) { - heif_image_handle_release(handle); - std::cerr << "Could not read depth channel\n"; - return 1; - } - - int depth_bit_depth = heif_image_handle_get_luma_bits_per_pixel(depth_handle); - - struct heif_image* depth_image; - err = heif_decode_image(depth_handle, - &depth_image, - encoder->colorspace(false), - encoder->chroma(false, depth_bit_depth), - nullptr); - if (err.code) { - heif_image_handle_release(depth_handle); - heif_image_handle_release(handle); - std::cerr << "Could not decode depth image: " << err.message << "\n"; - return 1; - } - - std::ostringstream s; - s << output_filename.substr(0, output_filename.find('.')); - s << "-depth"; - s << output_filename.substr(output_filename.find('.')); - - written = encoder->Encode(depth_handle, depth_image, s.str()); - if (!written) { - fprintf(stderr, "could not write depth image\n"); - } - else { - printf("Depth image written to %s\n", s.str().c_str()); - } - - heif_image_release(depth_image); - heif_image_handle_release(depth_handle); - } - - - // --- aux images - - int nAuxImages = heif_image_handle_get_number_of_auxiliary_images(handle, LIBHEIF_AUX_IMAGE_FILTER_OMIT_ALPHA | LIBHEIF_AUX_IMAGE_FILTER_OMIT_DEPTH); - if (nAuxImages>0) { - - std::vector auxIDs(nAuxImages); - heif_image_handle_get_list_of_auxiliary_image_IDs(handle, - LIBHEIF_AUX_IMAGE_FILTER_OMIT_ALPHA | LIBHEIF_AUX_IMAGE_FILTER_OMIT_DEPTH, - auxIDs.data(), nAuxImages); - - for (heif_item_id auxId : auxIDs) { - - struct heif_image_handle* aux_handle; - err = heif_image_handle_get_auxiliary_image_handle(handle, auxId, &aux_handle); - if (err.code) { - heif_image_handle_release(handle); - std::cerr << "Could not read auxiliary image\n"; - return 1; - } - - int aux_bit_depth = heif_image_handle_get_luma_bits_per_pixel(aux_handle); - - struct heif_image* aux_image; - err = heif_decode_image(aux_handle, - &aux_image, - encoder->colorspace(false), - encoder->chroma(false, aux_bit_depth), - nullptr); - if (err.code) { - heif_image_handle_release(aux_handle); - heif_image_handle_release(handle); - std::cerr << "Could not decode auxiliary image: " << err.message << "\n"; - return 1; - } - - const char* auxTypeC = nullptr; - err = heif_image_handle_get_auxiliary_type(aux_handle, &auxTypeC); - if (err.code) { - heif_image_handle_release(aux_handle); - heif_image_handle_release(handle); - std::cerr << "Could not get type of auxiliary image: " << err.message << "\n"; - return 1; - } - - std::string auxType = std::string(auxTypeC); - - heif_image_handle_free_auxiliary_types(aux_handle, &auxTypeC); - - std::ostringstream s; - s << output_filename.substr(0, output_filename.find('.')); - s << "-" + auxType; - s << output_filename.substr(output_filename.find('.')); - - written = encoder->Encode(aux_handle, aux_image, s.str()); - if (!written) { - fprintf(stderr, "could not write auxiliary image\n"); - } - else { - printf("Auxiliary image written to %s\n", s.str().c_str()); - } - - heif_image_release(aux_image); - heif_image_handle_release(aux_handle); - } - } - - - heif_image_handle_release(handle); - } - - image_index++; - } - - return 0; -} diff --git a/examples/heif_dec.cc b/examples/heif_dec.cc new file mode 100644 index 0000000000..14cbaa3c05 --- /dev/null +++ b/examples/heif_dec.cc @@ -0,0 +1,1090 @@ +/* + libheif example application. + + MIT License + + Copyright (c) 2017-2024 Dirk Farin + Copyright (c) 2017 struktur AG, Joachim Bauch + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#include +#include +#include +#include +#include "libheif/heif_items.h" +#include "libheif/heif_experimental.h" +#include "libheif/heif_sequences.h" + +#if defined(HAVE_UNISTD_H) + +#include + +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "heifio/encoder.h" + +#if HAVE_LIBJPEG +#include "heifio/encoder_jpeg.h" +#endif + +#if HAVE_LIBPNG +#include "heifio/encoder_png.h" +#endif + +#if HAVE_LIBTIFF +#include "heifio/encoder_tiff.h" +#endif + +#include "../heifio/encoder_y4m.h" +#include "common.h" +#include "common_utils.h" + +#if defined(_MSC_VER) +#include "getopt.h" +#endif + +#define UNUSED(x) (void)x + + +static void show_help(const char* argv0) +{ + std::filesystem::path p(argv0); + std::string filename = p.filename().string(); + + std::stringstream sstr; + sstr << " " << filename << " libheif version: " << heif_get_version(); + + std::string title = sstr.str(); + + std::cerr << title << "\n" + << std::string(title.length() + 1, '-') << "\n" + << "Usage: " << filename << " [options] [output-image]\n" + << "\n" + "The program determines the output file format from the output filename suffix.\n" + "These suffixes are recognized: jpg, jpeg, png, tif, tiff, y4m. If no output filename is specified, 'jpg' is used.\n" + "\n" + "Options:\n" + " -h, --help show help\n" + " -v, --version show version\n" + " -q, --quality quality (for JPEG output)\n" + " -o, --output FILENAME write output to FILENAME (optional)\n" + " -d, --decoder ID use a specific decoder (see --list-decoders)\n" + " --with-aux also write auxiliary images (e.g. depth images)\n" + " --with-xmp write XMP metadata to file (output filename with .xmp suffix)\n" + " --with-exif write EXIF metadata to file (output filename with .exif suffix)\n" + " --skip-exif-offset skip EXIF metadata offset bytes\n" + " --no-colons replace ':' characters in auxiliary image filenames with '_'\n" + " --list-decoders list all available decoders (built-in and plugins)\n" + " --tiles output all image tiles as separate images\n" + " --quiet do not output status messages to console\n" + " -S, --sequence decode image sequence instead of still image\n" + " --ignore-editlist show the raw media sequence timeline without repetitions\n" + " -C, --chroma-upsampling ALGO Force chroma upsampling algorithm (nn = nearest-neighbor / bilinear)\n" + " --png-compression-level # Set to integer between 0 (fastest) and 9 (best). Use -1 for default.\n" + " --transparency-composition-mode MODE Controls how transparent images are rendered when the output format\n" + " support transparency. MODE must be one of: white, black, checkerboard.\n" + " --disable-limits disable all security limits (do not use in production environment)\n" + " --codec-threads # number of threads to use in the codec plugin (0 = default)\n" + " --extract-mime-item TYPE extract the MIME item with the given content type into a file (mime-item.data)\n"; +} + + +class ContextReleaser +{ +public: + ContextReleaser(heif_context* ctx) : ctx_(ctx) + {} + + ~ContextReleaser() + { + heif_context_free(ctx_); + } + +private: + heif_context* ctx_; +}; + + +int option_quiet = 0; +int option_aux = 0; +int option_no_colons = 0; +int option_with_xmp = 0; +int option_with_exif = 0; +int option_skip_exif_offset = 0; +int option_list_decoders = 0; +int option_png_compression_level = -1; // use zlib default +int option_output_tiles = 0; +int option_disable_limits = 0; +int option_sequence = 0; +int option_ignore_editlist = 0; +int option_num_codec_threads = 0; +std::string option_extract_mime_item_type; +std::string output_filename; + +std::string chroma_upsampling; +std::string transparency_composition_mode = "checkerboard"; + +#define OPTION_PNG_COMPRESSION_LEVEL 1000 +#define OPTION_TRANSPARENCY_COMPOSITION_MODE 1001 +#define OPTION_CODEC_THREADS 1002 +#define OPTION_EXTRACT_MIME_ITEM 1003 + +static option long_options[] = { + {(char* const) "quality", required_argument, 0, 'q'}, + {(char* const) "strict", no_argument, 0, 's'}, + {(char* const) "decoder", required_argument, 0, 'd'}, + {(char* const) "output", required_argument, 0, 'o'}, + {(char* const) "quiet", no_argument, &option_quiet, 1}, + {(char* const) "with-aux", no_argument, &option_aux, 1}, + {(char* const) "with-xmp", no_argument, &option_with_xmp, 1}, + {(char* const) "with-exif", no_argument, &option_with_exif, 1}, + {(char* const) "skip-exif-offset", no_argument, &option_skip_exif_offset, 1}, + {(char* const) "no-colons", no_argument, &option_no_colons, 1}, + {(char* const) "list-decoders", no_argument, &option_list_decoders, 1}, + {(char* const) "tiles", no_argument, &option_output_tiles, 1}, + {(char* const) "sequence", no_argument, &option_sequence, 1}, + {(char* const) "help", no_argument, 0, 'h'}, + {(char* const) "chroma-upsampling", required_argument, 0, 'C'}, + {(char* const) "png-compression-level", required_argument, 0, OPTION_PNG_COMPRESSION_LEVEL}, + {(char* const) "transparency-composition-mode", required_argument, 0, OPTION_TRANSPARENCY_COMPOSITION_MODE}, + {(char* const) "version", no_argument, 0, 'v'}, + {(char* const) "disable-limits", no_argument, &option_disable_limits, 1}, + {(char* const) "ignore-editlist", no_argument, &option_ignore_editlist, 1}, + {(char* const) "codec-threads", required_argument, 0, OPTION_CODEC_THREADS}, + {(char* const) "extract-mime-item", required_argument, 0, OPTION_EXTRACT_MIME_ITEM}, + {nullptr, no_argument, nullptr, 0} +}; + + +#define MAX_DECODERS 20 + +bool is_integer_string(const char* s) +{ + if (strlen(s)==0) { + return false; + } + + if (!(isdigit(s[0]) || s[0]=='-')) { + return false; + } + + for (size_t i=strlen(s)-1; i>=1 ; i--) { + if (!isdigit(s[i])) { + return false; + } + } + + return true; +} + + +void show_png_compression_level_usage_warning() +{ + fprintf(stderr, "Invalid PNG compression level. Has to be between 0 (fastest) and 9 (best).\n" + "You can also use -1 to use the default compression level.\n"); +} + +std::string sanitizeFilename(const std::string& filename) { + static const std::unordered_set invalidChars = {'\\','/','*','?','"','<','>','|', ':'}; + std::string sanitized; + sanitized.reserve(filename.size()); // avoid reallocations + + for (char c : filename) { + if (invalidChars.find(c) != invalidChars.end()) { + sanitized += '_'; + } else { + sanitized += c; + } + } + return sanitized; +} + + +int decode_single_image(heif_image_handle* handle, + std::string filename_stem, + std::string filename_suffix, + heif_decoding_options* decode_options, + std::unique_ptr& encoder) +{ + int bit_depth = heif_image_handle_get_luma_bits_per_pixel(handle); + if (bit_depth < 0) { + std::cerr << "Input image has undefined bit-depth\n"; + return 1; + } + + int has_alpha = heif_image_handle_has_alpha_channel(handle); + + heif_image* image; + heif_error err; + err = heif_decode_image(handle, + &image, + encoder->colorspace(has_alpha), + encoder->chroma(has_alpha, bit_depth), + decode_options); + if (err.code) { + std::cerr << "Could not decode image: " + << err.message << "\n"; + return 1; + } + + // show decoding warnings + + for (int i = 0;; i++) { + int n = heif_image_get_decoding_warnings(image, i, &err, 1); + if (n == 0) { + break; + } + + std::cerr << "Warning: " << err.message << "\n"; + } + + if (image) { + std::string filename = filename_stem + '.' + filename_suffix; + + bool written = encoder->Encode(handle, image, filename); + if (!written) { + fprintf(stderr, "could not write image\n"); + } + else { + if (!option_quiet) { + std::cout << "Written to " << filename << "\n"; + } + } + heif_image_release(image); + + + if (option_aux) { + int has_depth = heif_image_handle_has_depth_image(handle); + if (has_depth) { + heif_item_id depth_id; + int nDepthImages = heif_image_handle_get_list_of_depth_image_IDs(handle, &depth_id, 1); + assert(nDepthImages == 1); + (void) nDepthImages; + + heif_image_handle* depth_handle = nullptr; + err = heif_image_handle_get_depth_image_handle(handle, depth_id, &depth_handle); + if (err.code) { + std::cerr << "Could not read depth channel\n"; + return 1; + } + + int depth_bit_depth = heif_image_handle_get_luma_bits_per_pixel(depth_handle); + + heif_image* depth_image; + err = heif_decode_image(depth_handle, + &depth_image, + encoder->colorspace(false), + encoder->chroma(false, depth_bit_depth), + nullptr); + if (err.code) { + heif_image_handle_release(depth_handle); + std::cerr << "Could not decode depth image: " << err.message << "\n"; + return 1; + } + + std::ostringstream s; + s << filename_stem; + s << "-depth."; + s << filename_suffix; + + written = encoder->Encode(depth_handle, depth_image, s.str()); + if (!written) { + fprintf(stderr, "could not write depth image\n"); + } + else { + if (!option_quiet) { + std::cout << "Depth image written to " << s.str() << "\n"; + } + } + + heif_image_release(depth_image); + heif_image_handle_release(depth_handle); + } + } + + + // --- aux images + + if (option_aux) { + int nAuxImages = heif_image_handle_get_number_of_auxiliary_images(handle, LIBHEIF_AUX_IMAGE_FILTER_OMIT_ALPHA | LIBHEIF_AUX_IMAGE_FILTER_OMIT_DEPTH); + if (nAuxImages > 0) { + + std::vector auxIDs(nAuxImages); + heif_image_handle_get_list_of_auxiliary_image_IDs(handle, + LIBHEIF_AUX_IMAGE_FILTER_OMIT_ALPHA | LIBHEIF_AUX_IMAGE_FILTER_OMIT_DEPTH, + auxIDs.data(), nAuxImages); + + for (heif_item_id auxId : auxIDs) { + + heif_image_handle* aux_handle = nullptr; + err = heif_image_handle_get_auxiliary_image_handle(handle, auxId, &aux_handle); + if (err.code) { + std::cerr << "Could not read auxiliary image\n"; + return 1; + } + + int aux_bit_depth = heif_image_handle_get_luma_bits_per_pixel(aux_handle); + + heif_image* aux_image = nullptr; + err = heif_decode_image(aux_handle, + &aux_image, + encoder->colorspace(false), + encoder->chroma(false, aux_bit_depth), + nullptr); + if (err.code) { + heif_image_handle_release(aux_handle); + std::cerr << "Could not decode auxiliary image: " << err.message << "\n"; + return 1; + } + + const char* auxTypeC = nullptr; + err = heif_image_handle_get_auxiliary_type(aux_handle, &auxTypeC); + if (err.code) { + heif_image_release(aux_image); + heif_image_handle_release(aux_handle); + std::cerr << "Could not get type of auxiliary image: " << err.message << "\n"; + return 1; + } + + std::string auxType = std::string(auxTypeC); + + heif_image_handle_release_auxiliary_type(aux_handle, &auxTypeC); + + if (option_no_colons) { + std::replace(auxType.begin(), auxType.end(), ':', '_'); + } + + std::ostringstream s; + s << filename_stem; + s << "-" + sanitizeFilename(auxType) + "."; + s << filename_suffix; + + std::string auxFilename = s.str(); + + written = encoder->Encode(aux_handle, aux_image, auxFilename); + if (!written) { + fprintf(stderr, "could not write auxiliary image\n"); + } + else { + if (!option_quiet) { + std::cout << "Auxiliary image written to " << auxFilename << "\n"; + } + } + + heif_image_release(aux_image); + heif_image_handle_release(aux_handle); + } + } + } + + + // --- write metadata + + if (option_with_xmp || option_with_exif) { + int numMetadata = heif_image_handle_get_number_of_metadata_blocks(handle, nullptr); + if (numMetadata > 0) { + std::vector ids(numMetadata); + heif_image_handle_get_list_of_metadata_block_IDs(handle, nullptr, ids.data(), numMetadata); + + for (int n = 0; n < numMetadata; n++) { + + // check whether metadata block is XMP + + std::string itemtype = heif_image_handle_get_metadata_type(handle, ids[n]); + std::string contenttype = heif_image_handle_get_metadata_content_type(handle, ids[n]); + + if (option_with_xmp && contenttype == "application/rdf+xml") { + // read XMP data to memory array + + size_t xmpSize = heif_image_handle_get_metadata_size(handle, ids[n]); + std::vector xmp(xmpSize); + err = heif_image_handle_get_metadata(handle, ids[n], xmp.data()); + if (err.code) { + std::cerr << "Could not read XMP metadata: " << err.message << "\n"; + return 1; + } + + // write XMP data to file + + std::string xmp_filename = filename_stem + ".xmp"; + std::ofstream ostr(xmp_filename.c_str()); + ostr.write((char*) xmp.data(), xmpSize); + } + else if (option_with_exif && itemtype == "Exif") { + // read EXIF data to memory array + + size_t exifSize = heif_image_handle_get_metadata_size(handle, ids[n]); + std::vector exif(exifSize); + err = heif_image_handle_get_metadata(handle, ids[n], exif.data()); + if (err.code) { + std::cerr << "Could not read EXIF metadata: " << err.message << "\n"; + return 1; + } + + uint32_t offset = 0; + if (option_skip_exif_offset) { + if (exifSize < 4) { + std::cerr << "Invalid EXIF metadata, it is too small.\n"; + return 1; + } + + offset = four_bytes_to_uint32(exif[0], exif[1], exif[2], exif[3]); + offset += 4; + + if (offset >= exifSize) { + std::cerr << "Invalid EXIF metadata, offset out of range.\n"; + return 1; + } + } + + // write EXIF data to file + + std::string exif_filename = filename_stem + ".exif"; + std::ofstream ostr(exif_filename.c_str()); + ostr.write((char*) exif.data() + offset, exifSize - offset); + } + } + } + } + } + + return 0; +} + + +int digits_for_integer(uint32_t v) +{ + int digits=1; + + while (v>=10) { + digits++; + v /= 10; + } + + return digits; +} + + +int decode_image_tiles(heif_image_handle* handle, + std::string filename_stem, + std::string filename_suffix, + heif_decoding_options* decode_options, + std::unique_ptr& encoder) +{ + heif_image_tiling tiling; + + heif_image_handle_get_image_tiling(handle, !decode_options->ignore_transformations, &tiling); + if (tiling.num_columns == 1 && tiling.num_rows == 1) { + return decode_single_image(handle, filename_stem, filename_suffix, decode_options, encoder); + } + + + int bit_depth = heif_image_handle_get_luma_bits_per_pixel(handle); + if (bit_depth < 0) { + std::cerr << "Input image has undefined bit-depth\n"; + return 1; + } + + int has_alpha = heif_image_handle_has_alpha_channel(handle); + + int digits_tx = digits_for_integer(tiling.num_columns-1); + int digits_ty = digits_for_integer(tiling.num_rows-1); + + for (uint32_t ty = 0; ty < tiling.num_rows; ty++) + for (uint32_t tx = 0; tx < tiling.num_columns; tx++) { + heif_image* image; + heif_error err; + err = heif_image_handle_decode_image_tile(handle, + &image, + encoder->colorspace(has_alpha), + encoder->chroma(has_alpha, bit_depth), + decode_options, tx, ty); + if (err.code) { + std::cerr << "Could not decode image tile: " + << err.message << "\n"; + return 1; + } + + // show decoding warnings + + for (int i = 0;; i++) { + int n = heif_image_get_decoding_warnings(image, i, &err, 1); + if (n == 0) { + break; + } + + std::cerr << "Warning: " << err.message << "\n"; + } + + if (image) { + std::stringstream filename_str; + filename_str << filename_stem << "-" + << std::setfill('0') << std::setw(digits_ty) << ty << '-' + << std::setfill('0') << std::setw(digits_tx) << tx << "." << filename_suffix; + + std::string filename = filename_str.str(); + + bool written = encoder->Encode(handle, image, filename); + if (!written) { + fprintf(stderr, "could not write image\n"); + } + else { + if (!option_quiet) { + std::cout << "Written to " << filename << "\n"; + } + } + heif_image_release(image); + } + } + + return 0; +} + +static int max_value_progress = 0; + +void start_progress(enum heif_progress_step step, int max_progress, void* progress_user_data) +{ + max_value_progress = max_progress; +} + +void on_progress(enum heif_progress_step step, int progress, void* progress_user_data) +{ + std::cout << "decoding image... " << progress * 100 / max_value_progress << "%\r"; + std::cout.flush(); +} + +void end_progress(enum heif_progress_step step, void* progress_user_data) +{ + std::cout << "\n"; +} + + +class LibHeifInitializer { +public: + LibHeifInitializer() { heif_init(nullptr); } + ~LibHeifInitializer() { heif_deinit(); } +}; + + +int main(int argc, char** argv) +{ + // This takes care of initializing libheif and also deinitializing it at the end to free all resources. + LibHeifInitializer initializer; + + int quality = -1; // Use default quality. + bool strict_decoding = false; + const char* decoder_id = nullptr; + + UNUSED(quality); // The quality will only be used by encoders that support it. + //while ((opt = getopt(argc, argv, "q:s")) != -1) { + while (true) { + int option_index = 0; + int c = getopt_long(argc, argv, "hq:sd:C:vo:S", long_options, &option_index); + if (c == -1) { + break; + } + + switch (c) { + case 'q': + quality = atoi(optarg); + break; + case 'd': + decoder_id = optarg; + break; + case 's': + strict_decoding = true; + break; + case '?': + std::cerr << "\n"; + [[fallthrough]]; + case 'h': + show_help(argv[0]); + return 0; + case 'C': + chroma_upsampling = optarg; + if (chroma_upsampling != "nn" && + chroma_upsampling != "nearest-neighbor" && + chroma_upsampling != "bilinear") { + fprintf(stderr, "Undefined chroma upsampling algorithm.\n"); + exit(5); + } + if (chroma_upsampling == "nn") { // abbreviation + chroma_upsampling = "nearest-neighbor"; + } + break; + case OPTION_PNG_COMPRESSION_LEVEL: + if (!is_integer_string(optarg)) { + show_png_compression_level_usage_warning(); + exit(5); + } + option_png_compression_level = std::stoi(optarg); + if (option_png_compression_level < -1 || option_png_compression_level > 9) { + show_png_compression_level_usage_warning(); + exit(5); + } + break; + case OPTION_TRANSPARENCY_COMPOSITION_MODE: + if (strcmp(optarg, "white") == 0 || + strcmp(optarg, "black") == 0 || + strcmp(optarg, "checkerboard") == 0) { + transparency_composition_mode = optarg; + } + else { + fprintf(stderr, "Unknown transparency composition mode. Must be one of: white, black, checkerboard.\n"); + exit(5); + } + break; + case 'v': + heif_examples::show_version(); + return 0; + case 'o': + output_filename = optarg; + break; + case 'S': + option_sequence = 1; + break; + case OPTION_CODEC_THREADS: + option_num_codec_threads = atoi(optarg); + break; + case OPTION_EXTRACT_MIME_ITEM: + option_extract_mime_item_type = optarg; + break; + } + } + + if (option_list_decoders) { + heif_examples::list_all_decoders(); + return 0; + } + + if (optind >= argc || optind + 2 < argc) { + // Need at least input filename as additional argument, but not more as two filenames. + show_help(argv[0]); + return 5; + } + + std::string input_filename(argv[optind++]); + std::string output_filename_stem; + std::string output_filename_suffix; + + if (output_filename.empty()) { + if (optind == argc) { + std::string input_stem; + size_t dot_pos = input_filename.rfind('.'); + if (dot_pos != std::string::npos) { + input_stem = input_filename.substr(0, dot_pos); + } + else { + input_stem = input_filename; + } + + output_filename = input_stem + ".jpg"; + } + else if (optind == argc-1) { + output_filename = argv[optind]; + } + else { + assert(false); + } + } + + std::unique_ptr encoder; + + size_t dot_pos = output_filename.rfind('.'); + if (dot_pos != std::string::npos) { + output_filename_stem = output_filename.substr(0,dot_pos); + std::string suffix_lowercase = output_filename.substr(dot_pos + 1); + + std::transform(suffix_lowercase.begin(), suffix_lowercase.end(), + suffix_lowercase.begin(), ::tolower); + + output_filename_suffix = suffix_lowercase; + + if (suffix_lowercase == "jpg" || suffix_lowercase == "jpeg") { +#if HAVE_LIBJPEG + static const int kDefaultJpegQuality = 90; + if (quality == -1) { + quality = kDefaultJpegQuality; + } + encoder.reset(new JpegEncoder(quality)); +#else + fprintf(stderr, "JPEG support has not been compiled in.\n"); + return 1; +#endif // HAVE_LIBJPEG + } + + if (suffix_lowercase == "png") { +#if HAVE_LIBPNG + auto pngEncoder = new PngEncoder(); + pngEncoder->set_compression_level(option_png_compression_level); + encoder.reset(pngEncoder); +#else + fprintf(stderr, "PNG support has not been compiled in.\n"); + return 1; +#endif // HAVE_LIBPNG + } + + if (suffix_lowercase == "tif" || suffix_lowercase == "tiff") { +#if HAVE_LIBTIFF + encoder.reset(new TiffEncoder()); +#else + fprintf(stderr, "TIFF support has not been compiled in.\n"); + return 1; +#endif // HAVE_LIBTIFF + } + + if (suffix_lowercase == "y4m") { + encoder.reset(new Y4MEncoder()); + } + } + else { + output_filename_stem = output_filename; + output_filename_suffix = "jpg"; + } + + if (!encoder) { + fprintf(stderr, "Unknown file type in %s\n", output_filename.c_str()); + return 1; + } + + + // --- check whether input is a supported HEIF file + + if (int ret = heif_examples::check_for_valid_input_HEIF_file(input_filename)) { + return ret; + } + + // --- read the HEIF file + + heif_context* ctx = heif_context_alloc(); + if (!ctx) { + fprintf(stderr, "Could not create context object\n"); + return 1; + } + + if (option_disable_limits) { + heif_context_set_security_limits(ctx, heif_get_disabled_security_limits()); + } + + ContextReleaser cr(ctx); + heif_error err; + err = heif_context_read_from_file(ctx, input_filename.c_str(), nullptr); + if (err.code != 0) { + std::cerr << "Could not read HEIF/AVIF file: " << err.message << "\n"; + return 1; + } + + + if (option_sequence) { + if (!heif_context_has_sequence(ctx)) { + std::cerr << "File contains no image sequence\n"; + return 1; + } + + int nTracks = heif_context_number_of_sequence_tracks(ctx); + std::cout << "number of tracks: " << nTracks << "\n"; + + std::vector track_ids(nTracks); + heif_context_get_track_ids(ctx, track_ids.data()); + + for (uint32_t id : track_ids) { + heif_track* track = heif_context_get_track(ctx, id); + std::cout << "#" << id << " : " << heif_examples::fourcc_to_string(heif_track_get_track_handler_type(track)); + + if (heif_track_get_track_handler_type(track) == heif_track_type_image_sequence || + heif_track_get_track_handler_type(track) == heif_track_type_video || + heif_track_get_track_handler_type(track) == heif_track_type_auxiliary) { + uint16_t w,h; + heif_track_get_image_resolution(track, &w, &h); + std::cout << " " << w << "x" << h; + } + else { + uint32_t sample_entry_type = heif_track_get_sample_entry_type_of_first_cluster(track); + std::cout << "\n sample entry type: " << heif_examples::fourcc_to_string(sample_entry_type); + if (sample_entry_type == heif_fourcc('u', 'r', 'i', 'm')) { + const char* uri; + err = heif_track_get_urim_sample_entry_uri_of_first_cluster(track, &uri); + if (err.code) { + std::cerr << "Cannot read 'urim'-track URI: " << err.message << "\n"; + } + std::cout << "\n URI: " << uri; + heif_string_release(uri); + } + + // get metadata track samples + + for (;;) { + heif_raw_sequence_sample* sample; + err = heif_track_get_next_raw_sequence_sample(track, &sample); + if (err.code != 0) { + break; + } + + size_t dataSize; + const uint8_t* data = heif_raw_sequence_sample_get_data(sample, &dataSize); + + std::cout << "\n raw sample: "; + for (uint32_t i = 0; i < dataSize; i++) { + std::cout << " " << std::hex << (int)data[i]; + } + + heif_raw_sequence_sample_release(sample); + } + } + + std::cout << "\n"; + + heif_track_release(track); + } + + std::unique_ptr decode_options(heif_decoding_options_alloc(), heif_decoding_options_free); + encoder->UpdateDecodingOptions(nullptr, decode_options.get()); + decode_options->ignore_sequence_editlist = option_ignore_editlist; + decode_options->strict_decoding = strict_decoding; + decode_options->decoder_id = decoder_id; + decode_options->num_codec_threads = option_num_codec_threads; + + heif_track* track = heif_context_get_track(ctx, 0); + + const char* track_contentId = heif_track_get_gimi_track_content_id(track); + if (track_contentId) { + std::cout << "track content ID: " << track_contentId << "\n"; + heif_string_release(track_contentId); + } + + const heif_tai_clock_info* taic = heif_track_get_tai_clock_info_of_first_cluster(track); + if (taic) { + std::cout << "taic: " << taic->time_uncertainty << " / " << taic->clock_resolution << " / " + << taic->clock_drift_rate << " / " << int(taic->clock_type) << "\n"; + } + + int with_alpha = heif_track_has_alpha_channel(track); + + for (int i=0; ;i++) { + heif_image* out_image = nullptr; + int bit_depth = 8; // TODO + err = heif_track_decode_next_image(track, &out_image, + encoder->colorspace(false), + encoder->chroma(with_alpha, bit_depth), + decode_options.get()); + if (err.code == heif_error_End_of_sequence) { + break; + } + else if (err.code) { + std::cerr << err.message << "\n"; + return 1; + } + + std::cout << "sample duration " << heif_image_get_duration(out_image) << "\n"; + + const char* contentID = heif_image_get_gimi_sample_content_id(out_image); + if (contentID) { + std::cout << "content ID " << contentID << "\n"; + heif_string_release(contentID); + } + + heif_tai_timestamp_packet* timestamp; + err = heif_image_get_tai_timestamp(out_image, ×tamp); + if (err.code) { + std::cerr << err.message << "\n"; + return 10; + } + else if (timestamp) { + std::cout << "TAI timestamp: " << timestamp->tai_timestamp << "\n"; + heif_tai_timestamp_packet_release(timestamp); + } + + std::ostringstream s; + s << output_filename_stem; + s << "-" << i+1; + s << "." << output_filename_suffix; + std::string numbered_filename = s.str(); + + bool written = encoder->Encode(nullptr, out_image, numbered_filename); + if (!written) { + fprintf(stderr, "could not write image\n"); + } + else { + if (!option_quiet) { + std::cout << "Written to " << numbered_filename << "\n"; + } + } + heif_image_release(out_image); + } + + heif_track_release(track); + + return 0; + } + + + int num_images = heif_context_get_number_of_top_level_images(ctx); + if (num_images == 0) { + fprintf(stderr, "File doesn't contain any images\n"); + return 1; + } + + if (!option_quiet) { + std::cout << "File contains " << num_images << " image" << (num_images>1 ? "s" : "") << "\n"; + } + + std::vector image_IDs(num_images); + num_images = heif_context_get_list_of_top_level_image_IDs(ctx, image_IDs.data(), num_images); + + + std::string filename; + size_t image_index = 1; // Image filenames are "1" based. + + for (int idx = 0; idx < num_images; ++idx) { + + std::string numbered_output_filename_stem; + + if (num_images > 1) { + std::ostringstream s; + s << output_filename_stem; + s << "-" << image_index; + numbered_output_filename_stem = s.str(); + + s << "." << output_filename_suffix; + filename = s.str(); + } + else { + filename = output_filename; + numbered_output_filename_stem = output_filename_stem; + } + + struct heif_image_handle* handle; + err = heif_context_get_image_handle(ctx, image_IDs[idx], &handle); + if (err.code) { + std::cerr << "Could not read HEIF/AVIF image " << idx << ": " + << err.message << "\n"; + return 1; + } + + std::unique_ptr decode_options(heif_decoding_options_alloc(), heif_decoding_options_free); + encoder->UpdateDecodingOptions(handle, decode_options.get()); + + decode_options->strict_decoding = strict_decoding; + decode_options->decoder_id = decoder_id; + decode_options->num_codec_threads = option_num_codec_threads; + + if (!option_quiet) { + decode_options->start_progress = start_progress; + decode_options->on_progress = on_progress; + decode_options->end_progress = end_progress; + } + + if (chroma_upsampling=="nearest-neighbor") { + decode_options->color_conversion_options.preferred_chroma_upsampling_algorithm = heif_chroma_upsampling_nearest_neighbor; + decode_options->color_conversion_options.only_use_preferred_chroma_algorithm = true; + } + else if (chroma_upsampling=="bilinear") { + decode_options->color_conversion_options.preferred_chroma_upsampling_algorithm = heif_chroma_upsampling_bilinear; + decode_options->color_conversion_options.only_use_preferred_chroma_algorithm = true; + } + + int ret; + + auto* color_conversion_options_ext = heif_color_conversion_options_ext_alloc(); + decode_options->color_conversion_options_ext = color_conversion_options_ext; + + + // If output file format does not support transparency, render in the selected composition mode. + + if (!encoder->supports_alpha()) { + if (transparency_composition_mode == "white") { + color_conversion_options_ext->alpha_composition_mode = heif_alpha_composition_mode_solid_color; + color_conversion_options_ext->background_red = 0xFFFFU; + color_conversion_options_ext->background_green = 0xFFFFU; + color_conversion_options_ext->background_blue = 0xFFFFU; + } + else if (transparency_composition_mode == "black") { + color_conversion_options_ext->alpha_composition_mode = heif_alpha_composition_mode_solid_color; + color_conversion_options_ext->background_red = 0; + color_conversion_options_ext->background_green = 0; + color_conversion_options_ext->background_blue = 0; + } + else if (transparency_composition_mode == "checkerboard") { + color_conversion_options_ext->alpha_composition_mode = heif_alpha_composition_mode_checkerboard; + } + } + + if (option_output_tiles) { + ret = decode_image_tiles(handle, numbered_output_filename_stem, output_filename_suffix, decode_options.get(), encoder); + } + else { + ret = decode_single_image(handle, numbered_output_filename_stem, output_filename_suffix, decode_options.get(), encoder); + } + + heif_color_conversion_options_ext_free(color_conversion_options_ext); + + if (ret) { + heif_image_handle_release(handle); + return ret; + } + + heif_image_handle_release(handle); + + image_index++; + } + + + // --- extract MIME item data + + if (!option_extract_mime_item_type.empty()) { + int nItems = heif_context_get_number_of_items(ctx); + if (nItems > 0) { + std::vector item_ids(nItems); + heif_context_get_list_of_item_IDs(ctx, item_ids.data(), nItems); + + for (auto id : item_ids) { + uint32_t type = heif_item_get_item_type(ctx, id); + if (type == heif_item_type_mime) { + const char* mimeType = heif_item_get_mime_item_content_type(ctx, id); + if (option_extract_mime_item_type == mimeType) { + uint8_t* data = nullptr; + size_t data_size; + heif_item_get_item_data(ctx, id, nullptr, &data, &data_size); + std::ofstream ostr("mime-item.data"); + ostr.write((const char*)data, data_size); + heif_release_item_data(ctx, &data); + } + } + } + } + + } + + return 0; +} diff --git a/examples/heif_enc.cc b/examples/heif_enc.cc index 7ba18f1f64..6beacbafae 100644 --- a/examples/heif_enc.cc +++ b/examples/heif_enc.cc @@ -3,7 +3,7 @@ MIT License - Copyright (c) 2017 struktur AG, Dirk Farin + Copyright (c) 2017 Dirk Farin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -23,1007 +23,1469 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#if defined(HAVE_CONFIG_H) -#include "config.h" -#endif -#include -#include +#include +#include +#include #include #include #include +#include #include #include #include #include +#include +#include +#include +#include +#include +#include +#include #include - -#if HAVE_LIBJPEG -extern "C" { -// Prevent duplicate definition for libjpeg-turbo v2.0 -// Note: these 'undef's are only a workaround for a libjpeg-turbo-v2.0 bug and -// should be removed again later. Bug has been fixed in libjpeg-turbo-v2.0.1. -#include -#if defined(LIBJPEG_TURBO_VERSION_NUMBER) && LIBJPEG_TURBO_VERSION_NUMBER == 2000000 -#undef HAVE_STDDEF_H -#undef HAVE_STDLIB_H -#endif -#include -} +#include +#include "libheif/heif_items.h" + +#include "heifio/decoder_jpeg.h" +#include "heifio/decoder_png.h" +#include "heifio/decoder_tiff.h" +#include "heifio/decoder_raw.h" +#include "heifio/decoder_y4m.h" + +#include "benchmark.h" +#include "common.h" +#include "SAI_datafile.h" +#include "libheif/api_structs.h" +#include "libheif/heif_experimental.h" +#include "libheif/heif_sequences.h" +#include "libheif/heif_uncompressed.h" + +#if HEIF_ENABLE_EXPERIMENTAL_FEATURES +#include "vmt.h" #endif -#if HAVE_LIBPNG -extern "C" { -#include -} -#endif - -#include - -#define JPEG_ICC_MARKER (JPEG_APP0+2) /* JPEG marker code for ICC */ -#define JPEG_ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */ +// --- command line parameters int master_alpha = 1; int thumb_alpha = 1; int list_encoders = 0; int two_colr_boxes = 0; int premultiplied_alpha = 0; +int run_benchmark = 0; +heif_metadata_compression metadata_compression_method = heif_metadata_compression_off; +int tiled_input_x_y = 0; const char* encoderId = nullptr; - -int nclx_matrix_coefficients = 6; -int nclx_colour_primaries = 2; -int nclx_transfer_characteristic = 2; +std::string chroma_downsampling; +int cut_tiles = 0; +int tiled_image_width = 0; +int tiled_image_height = 0; +std::string tiling_method = "grid"; +heif_unci_compression unci_compression = heif_unci_compression_zlib; +int add_pyramid_group = 0; + +uint16_t nclx_colour_primaries = 1; +uint16_t nclx_transfer_characteristic = 13; +uint16_t nclx_matrix_coefficients = 6; int nclx_full_range = true; -const int OPTION_NCLX_MATRIX_COEFFICIENTS = 1000; -const int OPTION_NCLX_COLOUR_PRIMARIES = 1001; -const int OPTION_NCLX_TRANSFER_CHARACTERISTIC = 1002; -const int OPTION_NCLX_FULL_RANGE_FLAG = 1003; - -static struct option long_options[] = { - {(char* const) "help", no_argument, 0, 'h'}, - {(char* const) "quality", required_argument, 0, 'q'}, - {(char* const) "output", required_argument, 0, 'o'}, - {(char* const) "lossless", no_argument, 0, 'L'}, - {(char* const) "thumb", required_argument, 0, 't'}, - {(char* const) "verbose", no_argument, 0, 'v'}, - {(char* const) "params", no_argument, 0, 'P'}, - {(char* const) "no-alpha", no_argument, &master_alpha, 0}, - {(char* const) "no-thumb-alpha", no_argument, &thumb_alpha, 0}, - {(char* const) "list-encoders", no_argument, &list_encoders, 1}, - {(char* const) "encoder", no_argument, 0, 'e'}, - {(char* const) "bit-depth", required_argument, 0, 'b'}, - {(char* const) "even-size", no_argument, 0, 'E'}, - {(char* const) "avif", no_argument, 0, 'A'}, - {(char* const) "matrix_coefficients", required_argument, 0, OPTION_NCLX_MATRIX_COEFFICIENTS}, - {(char* const) "colour_primaries", required_argument, 0, OPTION_NCLX_COLOUR_PRIMARIES}, - {(char* const) "transfer_characteristic", required_argument, 0, OPTION_NCLX_TRANSFER_CHARACTERISTIC}, - {(char* const) "full_range_flag", required_argument, 0, OPTION_NCLX_FULL_RANGE_FLAG}, - {(char* const) "enable-two-colr-boxes", no_argument, &two_colr_boxes, 1}, - {(char* const) "premultiplied-alpha", no_argument, &premultiplied_alpha, 1}, - {0, 0, 0, 0} +std::optional clli; +struct pixel_aspect_ratio +{ + uint32_t h,v; }; +std::optional pasp; + +// default to 30 fps +uint32_t sequence_timebase = 30; +uint32_t sequence_durations = 1; +uint32_t sequence_repetitions = 1; +std::string vmt_metadata_file; +bool binary_metadata_track = false; +std::string metadata_track_uri = "vmt:metadata"; + +int quality = 50; +bool lossless = false; +std::string output_filename; +int logging_level = 0; +bool option_show_parameters = false; +int thumbnail_bbox_size = 0; +int output_bit_depth = 10; +bool force_enc_av1f = false; +bool force_enc_avc = false; +bool force_enc_hevc = false; +bool force_enc_vvc = false; +bool force_enc_uncompressed = false; +bool force_enc_jpeg = false; +bool force_enc_jpeg2000 = false; +bool force_enc_htj2k = false; +bool use_tiling = false; +bool encode_sequence = false; +bool option_unif = false; +bool use_video_handler = false; +bool option_component_content_ids = false; +heif_orientation transform = heif_orientation_normal; +std::string option_mime_item_type; +std::string option_mime_item_file; +std::string option_mime_item_name; +#if HEIF_ENABLE_EXPERIMENTAL_FEATURES +std::string option_turtle_file; +bool option_embed_turtle = false; +#endif -void show_help(const char* argv0) +enum heif_sequence_gop_structure sequence_gop_structure = heif_sequence_gop_structure_lowdelay; +int sequence_keyframe_distance_min = 0; +int sequence_keyframe_distance_max = 0; +int sequence_max_frames = 0; // 0 -> no maximum +std::string option_gimi_track_id; +std::string option_sai_data_file; + +#if HEIF_WITH_OMAF +std::optional omaf_image_projection; +#endif +std::vector additional_compatible_brands; + +enum heif_output_nclx_color_profile_preset { - std::cerr << " heif-enc libheif version: " << heif_get_version() << "\n" - << "----------------------------------------\n" - << "Usage: heif-enc [options] image.jpeg ...\n" - << "\n" - << "When specifying multiple source images, they will all be saved into the same HEIF/AVIF file.\n" - << "\n" - << "When using the x265 encoder, you may pass it any of its parameters by\n" - << "prefixing the parameter name with 'x265:'. Hence, to set the 'ctu' parameter,\n" - << "you will have to set 'x265:ctu' in libheif (e.g.: -p x265:ctu=64).\n" - << "Note that there is no checking for valid parameters when using the prefix.\n" - << "\n" - << "Options:\n" - << " -h, --help show help\n" - << " -q, --quality set output quality (0-100) for lossy compression\n" - << " -L, --lossless generate lossless output (-q has no effect)\n" - << " -t, --thumb # generate thumbnail with maximum size # (default: off)\n" - << " --no-alpha do not save alpha channel\n" - << " --no-thumb-alpha do not save alpha channel in thumbnail image\n" - << " -o, --output output filename (optional)\n" - << " -v, --verbose enable logging output (more -v will increase logging level)\n" - << " -P, --params show all encoder parameters\n" - << " -b, --bit-depth # bit-depth of generated HEIF/AVIF file when using 16-bit PNG input (default: 10 bit)\n" - << " -p set encoder parameter (NAME=VALUE)\n" - << " -A, --avif encode as AVIF\n" - << " --list-encoders list all available encoders for the selected output format\n" - << " -e, --encoder ID select encoder to use (the IDs can be listed with --list-encoders)\n" - << " -E, --even-size [deprecated] crop images to even width and height (odd sizes are not decoded correctly by some software)\n" - << " --matrix_coefficients nclx profile: color conversion matrix coefficients, default=6 (see h.273)\n" - << " --colour_primaries nclx profile: color primaries (see h.273)\n" - << " --transfer_characteristic nclx profile: transfer characteristics (see h.273)\n" - << " --full_range_flag nclx profile: full range flag, default: 1\n" - << " --enable-two-colr-boxes will write both an ICC and an nclx color profile if both a present\n" - << " --premultiplied-alpha input image has premultiplied alpha\n" - << "\n" - << "Note: to get lossless encoding, you need this set of options:\n" - << " -L switch encoder to lossless mode\n" - << " -p chroma=444 switch off color subsampling\n" - << " --matrix_coefficients=0 encode in RGB color-space\n"; -} + heif_output_nclx_color_profile_preset_custom, // Default. Use the values provided by the user. + heif_output_nclx_color_profile_preset_automatic, // Choose the profile depending on the input. + heif_output_nclx_color_profile_preset_compatible, // Choose a profile that is decoded correctly by most systems. + heif_output_nclx_color_profile_preset_Rec_601, // SD / JPEG / MPEG + heif_output_nclx_color_profile_preset_Rec_709, // HDTV / (sRGB) + heif_output_nclx_color_profile_preset_Rec_2020 // UHDTV +}; +heif_output_nclx_color_profile_preset output_color_profile_preset = heif_output_nclx_color_profile_preset_custom; -#if HAVE_LIBJPEG -static bool JPEGMarkerIsIcc(jpeg_saved_marker_ptr marker) -{ - return - marker->marker == JPEG_ICC_MARKER && - marker->data_length >= JPEG_ICC_OVERHEAD_LEN && - /* verify the identifying string */ - GETJOCTET(marker->data[0]) == 0x49 && - GETJOCTET(marker->data[1]) == 0x43 && - GETJOCTET(marker->data[2]) == 0x43 && - GETJOCTET(marker->data[3]) == 0x5F && - GETJOCTET(marker->data[4]) == 0x50 && - GETJOCTET(marker->data[5]) == 0x52 && - GETJOCTET(marker->data[6]) == 0x4F && - GETJOCTET(marker->data[7]) == 0x46 && - GETJOCTET(marker->data[8]) == 0x49 && - GETJOCTET(marker->data[9]) == 0x4C && - GETJOCTET(marker->data[10]) == 0x45 && - GETJOCTET(marker->data[11]) == 0x0; -} +std::string property_pitm_description; -boolean ReadICCProfileFromJPEG(j_decompress_ptr cinfo, - JOCTET** icc_data_ptr, - unsigned int* icc_data_len) +RawImageParameters raw_input_params; +bool force_raw_input = false; + +std::optional parse_int(const char* s) { - jpeg_saved_marker_ptr marker; - int num_markers = 0; - int seq_no; - JOCTET* icc_data; - unsigned int total_length; -#define MAX_SEQ_NO 255 /* sufficient since marker numbers are bytes */ - char marker_present[MAX_SEQ_NO + 1]; /* 1 if marker found */ - unsigned int data_length[MAX_SEQ_NO + 1]; /* size of profile data in marker */ - unsigned int data_offset[MAX_SEQ_NO + 1]; /* offset for data in marker */ - - *icc_data_ptr = NULL; /* avoid confusion if FALSE return */ - *icc_data_len = 0; - - /* This first pass over the saved markers discovers whether there are - * any ICC markers and verifies the consistency of the marker numbering. - */ - - for (seq_no = 1; seq_no <= MAX_SEQ_NO; seq_no++) - marker_present[seq_no] = 0; - - for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) { - if (JPEGMarkerIsIcc(marker)) { - if (num_markers == 0) - num_markers = GETJOCTET(marker->data[13]); - else if (num_markers != GETJOCTET(marker->data[13])) - return FALSE; /* inconsistent num_markers fields */ - seq_no = GETJOCTET(marker->data[12]); - if (seq_no <= 0 || seq_no > num_markers) - return FALSE; /* bogus sequence number */ - if (marker_present[seq_no]) - return FALSE; /* duplicate sequence numbers */ - marker_present[seq_no] = 1; - data_length[seq_no] = marker->data_length - JPEG_ICC_OVERHEAD_LEN; - } - } - - if (num_markers == 0) - return FALSE; - - /* Check for missing markers, count total space needed, - * compute offset of each marker's part of the data. - */ - - total_length = 0; - for (seq_no = 1; seq_no <= num_markers; seq_no++) { - if (marker_present[seq_no] == 0) - return FALSE; /* missing sequence number */ - data_offset[seq_no] = total_length; - total_length += data_length[seq_no]; - } - - if (total_length <= 0) - return FALSE; /* found only empty markers? */ - - /* Allocate space for assembled data */ - icc_data = (JOCTET*) malloc(total_length * sizeof(JOCTET)); - if (icc_data == NULL) - return FALSE; /* oops, out of memory */ - - /* and fill it in */ - for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) { - if (JPEGMarkerIsIcc(marker)) { - JOCTET FAR* src_ptr; - JOCTET* dst_ptr; - unsigned int length; - seq_no = GETJOCTET(marker->data[12]); - dst_ptr = icc_data + data_offset[seq_no]; - src_ptr = marker->data + JPEG_ICC_OVERHEAD_LEN; - length = data_length[seq_no]; - while (length--) { - *dst_ptr++ = *src_ptr++; - } - } + char* end; + long val = strtol(s, &end, 10); + if (*end != '\0' || end == s) { + return {}; } - - *icc_data_ptr = icc_data; - *icc_data_len = total_length; - - return TRUE; + return val; } -std::shared_ptr loadJPEG(const char* filename) -{ - struct heif_image* image = nullptr; +// for benchmarking +#if !defined(_MSC_VER) +#define HAVE_GETTIMEOFDAY 1 // TODO: should be set by CMake +#endif + +#if HAVE_GETTIMEOFDAY +#include +timeval time_encoding_start; +timeval time_encoding_end; +#endif - // ### Code copied from LibVideoGfx and slightly modified to use HeifPixelImage +const int OPTION_NCLX_MATRIX_COEFFICIENTS = 1000; +const int OPTION_NCLX_COLOUR_PRIMARIES = 1001; +const int OPTION_NCLX_TRANSFER_CHARACTERISTIC = 1002; +const int OPTION_NCLX_FULL_RANGE_FLAG = 1003; +const int OPTION_PLUGIN_DIRECTORY = 1004; +const int OPTION_PITM_DESCRIPTION = 1005; +const int OPTION_USE_JPEG_COMPRESSION = 1006; +const int OPTION_USE_JPEG2000_COMPRESSION = 1007; +const int OPTION_VERBOSE = 1008; +const int OPTION_USE_HTJ2K_COMPRESSION = 1009; +const int OPTION_USE_VVC_COMPRESSION = 1010; +const int OPTION_TILED_IMAGE_WIDTH = 1011; +const int OPTION_TILED_IMAGE_HEIGHT = 1012; +const int OPTION_TILING_METHOD = 1013; +const int OPTION_UNCI_COMPRESSION = 1014; +const int OPTION_CUT_TILES = 1015; +const int OPTION_SEQUENCES_TIMEBASE = 1016; +const int OPTION_SEQUENCES_DURATIONS = 1017; +const int OPTION_SEQUENCES_FPS = 1018; +const int OPTION_VMT_METADATA_FILE = 1019; +const int OPTION_SEQUENCES_REPETITIONS = 1020; +const int OPTION_COLOR_PROFILE_PRESET = 1021; +const int OPTION_SET_CLLI = 1022; +const int OPTION_SET_PASP = 1023; +const int OPTION_SEQUENCES_GOP_STRUCTURE = 1024; +const int OPTION_SEQUENCES_MIN_KEYFRAME_DISTANCE = 1025; +const int OPTION_SEQUENCES_MAX_KEYFRAME_DISTANCE = 1026; +const int OPTION_SEQUENCES_MAX_FRAMES = 1027; +const int OPTION_USE_AVC_COMPRESSION = 1028; +const int OPTION_BINARY_METADATA_TRACK = 1029; +const int OPTION_METADATA_TRACK_URI = 1030; +const int OPTION_ADD_MIME_ITEM = 1031; +const int OPTION_MIME_ITEM_FILE = 1032; +const int OPTION_MIME_ITEM_NAME = 1033; +const int OPTION_METADATA_COMPRESSION = 1034; +const int OPTION_SEQUENCES_GIMI_TRACK_ID = 1035; +const int OPTION_SEQUENCES_SAI_DATA_FILE = 1036; +const int OPTION_USE_HEVC_COMPRESSION = 1037; +#if HEIF_WITH_OMAF +const int OPTION_SET_OMAF_IMAGE_PROJECTION = 1038; +#endif +const int OPTION_ADD_COMPATIBLE_BRAND = 1039; +const int OPTION_UNIF = 1040; +const int OPTION_RAW_WIDTH = 1041; +const int OPTION_RAW_HEIGHT = 1042; +const int OPTION_RAW_TYPE = 1043; +const int OPTION_RAW_ENDIAN = 1044; +const int OPTION_RAW = 1045; +const int OPTION_DO_ROTATE = 1046; +const int OPTION_DO_FLIP_H = 1047; +const int OPTION_DO_FLIP_V = 1048; + + +#if HEIF_ENABLE_EXPERIMENTAL_FEATURES +const int OPTION_TURTLE = 2000; +const int OPTION_EMBED_TURTLE = 2001; +#endif - struct jpeg_decompress_struct cinfo; - struct jpeg_error_mgr jerr; +const int OPTION_COMPONENT_CONTENT_IDS = 2002; - // to store embedded icc profile - uint32_t iccLen; - uint8_t* iccBuffer = NULL; - // open input file +static std::string generate_random_uuid_urn() +{ + std::random_device rd; + std::mt19937 gen(rd()); + std::uniform_int_distribution dist(0, 0xFFFFFFFF); - FILE* infile; - if ((infile = fopen(filename, "rb")) == NULL) { - std::cerr << "Can't open " << filename << "\n"; - exit(1); + uint32_t data[4]; + for (auto& d : data) { + d = dist(gen); } + auto* bytes = reinterpret_cast(data); + + // Set version 4 (random) and variant 1 (RFC 4122) + bytes[6] = (bytes[6] & 0x0F) | 0x40; + bytes[8] = (bytes[8] & 0x3F) | 0x80; + + char buf[64]; + snprintf(buf, sizeof(buf), + "urn:uuid:%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x", + bytes[0], bytes[1], bytes[2], bytes[3], + bytes[4], bytes[5], + bytes[6], bytes[7], + bytes[8], bytes[9], + bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15]); + return buf; +} - // initialize decompressor - - jpeg_create_decompress(&cinfo); - cinfo.err = jpeg_std_error(&jerr); - jpeg_stdio_src(&cinfo, infile); +#if HEIF_ENABLE_EXPERIMENTAL_FEATURES +struct TurtleContentIDs { + std::string image_content_id; + // key: {layer, tx, ty} + std::map, std::string> tile_content_ids; +}; - /* Adding this part to prepare for icc profile reading. */ - jpeg_save_markers(&cinfo, JPEG_APP0 + 2, 0xFFFF); - jpeg_read_header(&cinfo, TRUE); +enum class TurtlePendingType { + none, + image, + tile +}; - boolean embeddedIccFlag = ReadICCProfileFromJPEG(&cinfo, &iccBuffer, &iccLen); - if (cinfo.jpeg_color_space == JCS_GRAYSCALE) { - cinfo.out_color_space = JCS_GRAYSCALE; +TurtleContentIDs parse_turtle_content_ids(const std::string& filename) +{ + TurtleContentIDs result; - jpeg_start_decompress(&cinfo); + std::ifstream file(filename); + if (!file) { + std::cerr << "Warning: could not open turtle file: " << filename << "\n"; + return result; + } - JSAMPARRAY buffer; - buffer = (*cinfo.mem->alloc_sarray) - ((j_common_ptr) &cinfo, JPOOL_IMAGE, cinfo.output_width * cinfo.output_components, 1); + std::string line; + TurtlePendingType pending_type = TurtlePendingType::none; + int pending_layer = 0, pending_tx = 0, pending_ty = 0; + while (std::getline(file, line)) { + // trim leading whitespace + size_t start = line.find_first_not_of(" \t"); + if (start == std::string::npos) { + continue; // empty line + } + line = line.substr(start); - // create destination image + if (line.rfind("# image", 0) == 0) { + pending_type = TurtlePendingType::image; + } + else if (line.rfind("# tile ", 0) == 0) { + if (sscanf(line.c_str(), "# tile %d %d %d", &pending_layer, &pending_tx, &pending_ty) == 3) { + pending_type = TurtlePendingType::tile; + } + } + else if (pending_type != TurtlePendingType::none && line[0] == '<') { + // extract URI between < and > + auto close = line.find('>'); + if (close != std::string::npos) { + std::string uri = line.substr(1, close - 1); + if (pending_type == TurtlePendingType::image) { + result.image_content_id = uri; + } + else if (pending_type == TurtlePendingType::tile) { + result.tile_content_ids[{pending_layer, pending_tx, pending_ty}] = uri; + } + } + pending_type = TurtlePendingType::none; + } + } - struct heif_error err = heif_image_create(cinfo.output_width, cinfo.output_height, - heif_colorspace_monochrome, - heif_chroma_monochrome, - &image); - (void) err; - // TODO: handle error + return result; +} +#endif - heif_image_add_plane(image, heif_channel_Y, cinfo.output_width, cinfo.output_height, 8); +static option long_options[] = { + {(char* const) "help", no_argument, 0, 'h'}, + {(char* const) "version", no_argument, 0, 'v'}, + {(char* const) "quality", required_argument, 0, 'q'}, + {(char* const) "output", required_argument, 0, 'o'}, + {(char* const) "lossless", no_argument, 0, 'L'}, + {(char* const) "thumb", required_argument, 0, 't'}, + {(char* const) "verbose", no_argument, 0, OPTION_VERBOSE}, + {(char* const) "params", no_argument, 0, 'P'}, + {(char* const) "no-alpha", no_argument, &master_alpha, 0}, + {(char* const) "no-thumb-alpha", no_argument, &thumb_alpha, 0}, + {(char* const) "list-encoders", no_argument, &list_encoders, 1}, + {(char* const) "encoder", required_argument, 0, 'e'}, + {(char* const) "bit-depth", required_argument, 0, 'b'}, + {(char* const) "even-size", no_argument, 0, 'E'}, + {(char* const) "avif", no_argument, 0, 'A'}, + {(char* const) "hevc", no_argument, 0, OPTION_USE_HEVC_COMPRESSION}, + {(char* const) "vvc", no_argument, 0, OPTION_USE_VVC_COMPRESSION}, + {(char* const) "avc", no_argument, 0, OPTION_USE_AVC_COMPRESSION}, + {(char* const) "jpeg", no_argument, 0, OPTION_USE_JPEG_COMPRESSION}, + {(char* const) "jpeg2000", no_argument, 0, OPTION_USE_JPEG2000_COMPRESSION}, + {(char* const) "htj2k", no_argument, 0, OPTION_USE_HTJ2K_COMPRESSION}, +#if WITH_UNCOMPRESSED_CODEC + {(char* const) "uncompressed", no_argument, 0, 'U'}, + {(char* const) "unci-compression-method", required_argument, nullptr, OPTION_UNCI_COMPRESSION}, +#if HEIF_ENABLE_EXPERIMENTAL_FEATURES + {(char* const) "component-content-ids", no_argument, nullptr, OPTION_COMPONENT_CONTENT_IDS}, +#endif +#endif + {(char* const) "color-profile", required_argument, 0, OPTION_COLOR_PROFILE_PRESET}, + {(char* const) "matrix_coefficients", required_argument, 0, OPTION_NCLX_MATRIX_COEFFICIENTS}, + {(char* const) "colour_primaries", required_argument, 0, OPTION_NCLX_COLOUR_PRIMARIES}, + {(char* const) "transfer_characteristic", required_argument, 0, OPTION_NCLX_TRANSFER_CHARACTERISTIC}, + {(char* const) "full_range_flag", required_argument, 0, OPTION_NCLX_FULL_RANGE_FLAG}, + {(char* const) "enable-two-colr-boxes", no_argument, &two_colr_boxes, 1}, + {(char* const) "clli", required_argument, 0, OPTION_SET_CLLI}, + {(char* const) "pasp", required_argument, 0, OPTION_SET_PASP}, + {(char* const) "premultiplied-alpha", no_argument, &premultiplied_alpha, 1}, + {(char* const) "rotate-cw", required_argument, 0, OPTION_DO_ROTATE}, + {(char* const) "flip-h", no_argument, 0, OPTION_DO_FLIP_H}, + {(char* const) "flip-v", no_argument, 0, OPTION_DO_FLIP_V}, + {(char* const) "plugin-directory", required_argument, 0, OPTION_PLUGIN_DIRECTORY}, + {(char* const) "benchmark", no_argument, &run_benchmark, 1}, + {(char* const) "enable-metadata-compression", required_argument, 0, OPTION_METADATA_COMPRESSION}, + {(char* const) "pitm-description", required_argument, 0, OPTION_PITM_DESCRIPTION}, + {(char* const) "chroma-downsampling", required_argument, 0, 'C'}, + {(char* const) "cut-tiles", required_argument, nullptr, OPTION_CUT_TILES}, + {(char* const) "tiled-input", no_argument, 0, 'T'}, + {(char* const) "tiled-image-width", required_argument, nullptr, OPTION_TILED_IMAGE_WIDTH}, + {(char* const) "tiled-image-height", required_argument, nullptr, OPTION_TILED_IMAGE_HEIGHT}, + {(char* const) "tiled-input-x-y", no_argument, &tiled_input_x_y, 1}, + {(char* const) "tiling-method", required_argument, nullptr, OPTION_TILING_METHOD}, + {(char* const) "add-pyramid-group", no_argument, &add_pyramid_group, 1}, + {(char* const) "sequence", no_argument, 0, 'S'}, + {(char* const) "video", no_argument, 0, 'V'}, + {(char* const) "timebase", required_argument, nullptr, OPTION_SEQUENCES_TIMEBASE}, + {(char* const) "duration", required_argument, nullptr, OPTION_SEQUENCES_DURATIONS}, + {(char* const) "fps", required_argument, nullptr, OPTION_SEQUENCES_FPS}, + {(char* const) "repetitions", required_argument, nullptr, OPTION_SEQUENCES_REPETITIONS}, + {(char* const) "max-frames", required_argument, nullptr, OPTION_SEQUENCES_MAX_FRAMES}, +#if HEIF_ENABLE_EXPERIMENTAL_FEATURES + {(char* const) "vmt-metadata", required_argument, nullptr, OPTION_VMT_METADATA_FILE}, + {(char* const) "binary-metadata-track", no_argument, nullptr, OPTION_BINARY_METADATA_TRACK}, + {(char* const) "metadata-track-uri", required_argument, nullptr, OPTION_METADATA_TRACK_URI}, + {(char* const) "add-mime-item", required_argument, nullptr, OPTION_ADD_MIME_ITEM}, + {(char* const) "mime-item-file", required_argument, nullptr, OPTION_MIME_ITEM_FILE}, + {(char* const) "mime-item-name", required_argument, nullptr, OPTION_MIME_ITEM_NAME}, + {(char* const) "turtle", required_argument, nullptr, OPTION_TURTLE}, + {(char* const) "embed-turtle", no_argument, nullptr, OPTION_EMBED_TURTLE}, +#endif + {(char* const) "gop-structure", required_argument, nullptr, OPTION_SEQUENCES_GOP_STRUCTURE}, + {(char* const) "min-keyframe-distance", required_argument, nullptr, OPTION_SEQUENCES_MIN_KEYFRAME_DISTANCE}, + {(char* const) "max-keyframe-distance", required_argument, nullptr, OPTION_SEQUENCES_MAX_KEYFRAME_DISTANCE}, + {(char* const) "set-gimi-track-id", required_argument, nullptr, OPTION_SEQUENCES_GIMI_TRACK_ID}, + {(char* const) "sai-data-file", required_argument, nullptr, OPTION_SEQUENCES_SAI_DATA_FILE}, +#if HEIF_WITH_OMAF + {(char* const) "omaf-image-projection", required_argument, nullptr, OPTION_SET_OMAF_IMAGE_PROJECTION}, +#endif + {(char* const) "add-compatible-brand", required_argument, nullptr, OPTION_ADD_COMPATIBLE_BRAND}, + {(char* const) "unif", no_argument, nullptr, OPTION_UNIF}, + {(char* const) "raw", no_argument, nullptr, OPTION_RAW}, + {(char* const) "raw-width", required_argument, nullptr, OPTION_RAW_WIDTH}, + {(char* const) "raw-height", required_argument, nullptr, OPTION_RAW_HEIGHT}, + {(char* const) "raw-type", required_argument, nullptr, OPTION_RAW_TYPE}, + {(char* const) "raw-endian", required_argument, nullptr, OPTION_RAW_ENDIAN}, + {0, 0, 0, 0} +}; - int y_stride; - uint8_t* py = heif_image_get_plane(image, heif_channel_Y, &y_stride); +void show_help(const char* argv0) +{ + std::filesystem::path p(argv0); + std::string filename = p.filename().string(); - // read the image + std::stringstream sstr; + sstr << " " << filename << " libheif version: " << heif_get_version(); - while (cinfo.output_scanline < cinfo.output_height) { - (void) jpeg_read_scanlines(&cinfo, buffer, 1); + std::string title = sstr.str(); - memcpy(py + (cinfo.output_scanline - 1) * y_stride, *buffer, cinfo.output_width); - } + std::cerr << title << "\n" + << std::string(title.length() + 1, '-') << "\n" + << "Usage: " << filename << " [options] ...\n" + << "\n" + << "When specifying multiple source images, they will all be saved into the same HEIF/AVIF file.\n" + << "\n" + << "Some encoders (x265, aom) let you pass-through any parameters by prefixing them with the encoder name.\n" + << "For example, you may pass any x265 parameter by prefixing it with 'x265:'. For example, to set\n" + << "the 'ctu' parameter, you will have to set 'x265:ctu' in libheif (e.g.: -p x265:ctu=64).\n" + << "Note that when using the prefix, libheif cannot tell you which parameters and values are supported.\n" + << "\n" + << "Options:\n" + << " -h, --help show help\n" + << " -v, --version show version\n" + << " -o, --output output filename (optional)\n" + << " -q, --quality set output quality (0-100) for lossy compression\n" + << " -L, --lossless generate lossless output (-q has no effect). Image will be encoded as RGB (matrix_coefficients=0).\n" + << " -t, --thumb # generate thumbnail with maximum size # (default: off)\n" + << " --no-alpha do not save alpha channel\n" + << " --no-thumb-alpha do not save alpha channel in thumbnail image\n" + << " --verbose enable logging output (more will increase logging level)\n" + << " -b, --bit-depth # number of bits to use from an 16-bit PNG input, valid range: 9-16 (default: 10 bit)\n" + << " --premultiplied-alpha input image has premultiplied alpha\n" +#if WITH_HEADER_COMPRESSION + << " --enable-metadata-compression ALGO enable metadata item compression (experimental)\n" + << " Choose algorithm from {off"; // TODO: add 'auto', but it currently equals 'off' + if (heif_metadata_compression_method_supported(heif_metadata_compression_deflate)) { + std::cerr << ",deflate,zlib"; } - else { - cinfo.out_color_space = JCS_YCbCr; + if (heif_metadata_compression_method_supported(heif_metadata_compression_brotli)) { + std::cerr << ",brotli"; + } + std::cerr << "}.\n" +#endif + << " -C, --chroma-downsampling ALGO force chroma downsampling algorithm (nn = nearest-neighbor / average / sharp-yuv)\n" + << " (sharp-yuv makes edges look sharper when using YUV420 with bilinear chroma upsampling)\n" + << " --benchmark measure encoding time, PSNR, and output file size\n" + << " --pitm-description TEXT set user description for primary image (experimental)\n" +#if HEIF_ENABLE_EXPERIMENTAL_FEATURES + << " --add-mime-item TYPE add a mime item of the specified content type (experimental)\n" + << " --mime-item-file FILE use the specified FILE as the data to put into the mime item (experimental)\n" + << " --mime-item-name NAME assign the name to the embedded item (experimental)\n" + << " --turtle FILENAME read Turtle (RDF) file and assign GIMI content IDs to tiles (experimental)\n" + << " --embed-turtle also embed the Turtle file as metadata item in the HEIF (experimental)\n" +#endif + << " --add-compatible-brand BRAND add a compatible brand to the output file (4 characters)\n" + << " --unif use unified ID namespace (adds 'unif' compatible brand)\n" + << "\n" + << "codecs:\n" + << " -A, --avif encode as AVIF (not needed if output filename with .avif suffix is provided)\n" + << " --hevc encode as HEVC (default)\n" + << " --vvc encode as VVC (experimental)\n" + << " --avc encode as AVC (experimental)\n" + << " --jpeg encode as JPEG\n" + << " --jpeg2000 encode as JPEG 2000 (experimental)\n" + << " --htj2k encode as High Throughput JPEG 2000 (experimental)\n" +#if WITH_UNCOMPRESSED_CODEC + << " -U, --uncompressed encode as uncompressed image (according to ISO 23001-17) (EXPERIMENTAL)\n" + << " --unci-compression METHOD choose one of these methods: none, deflate, zlib, brotli.\n" +#endif + << " --list-encoders list all available encoders for all compression formats\n" + << " -e, --encoder ID select encoder to use (the IDs can be listed with --list-encoders)\n" + << " --plugin-directory DIR load all codec plugins in the directory\n" + << " -P, --params show all encoder parameters and exit, input file not required or used.\n" + << " -p NAME=VALUE set encoder parameter\n" + << "\n" + << "raw input:\n" + << " --raw force raw pixel data input (for files without .raw/.img suffix)\n" + << " --raw-width # width of raw input image (computed from file size if omitted)\n" + << " --raw-height # height of raw input image (computed from file size if omitted)\n" + << " --raw-type TYPE pixel data type: uint8, sint8, uint16, sint16, uint32, sint32, float32, float64\n" + << " --raw-endian ENDIAN byte order of input data: little (default), big\n" + << "\n" + << "color profile:\n" + << " --color-profile NAME use a color profile preset for the output. Valid values are:\n" + << " custom: (default) use the provided matrix_coefficients, colour_primaries, transfer_characteristic\n" + << " auto: automatically guess suitable values from the input image characteristics\n" + << " compatible: use a profile that is decoded correctly by most applications by avoiding incomplete implementations\n" + << " 601: use Rec.601 (SD), close to JPEG\n" + << " 709: use Rec.709 (HDTV), close to sRGB\n" + << " 2020: use Rec.2020 (UHDTV), transfer curve will be selected based on bits per pixel\n" + << " --matrix_coefficients nclx profile: color conversion matrix coefficients, default=6 (see h.273)\n" + << " --colour_primaries nclx profile: color primaries (see h.273)\n" + << " --transfer_characteristic nclx profile: transfer characteristics (see h.273)\n" + << " --full_range_flag nclx profile: full range flag, default: 1\n" + << " --enable-two-colr-boxes will write both an ICC and an nclx color profile if both are present\n" + << " --clli MaxCLL,MaxPALL add 'content light level information' property to all encoded images\n" + << " --pasp h,v set pixel aspect ratio property to all encoded images\n" + << " --rotate-cw # rotate input image by 0, 90, 180, 270 degrees (clock-wise)\n" + << " --flip-h / --flip-v flip input image horizontally or vertically\n" + << "\n" + << "tiling:\n" + << " --cut-tiles # cuts the input image into square tiles of the given width\n" + << " -T, --tiled-input input is a set of tile images (only provide one filename with two tile position numbers).\n" + << " For example, 'tile-01-05.jpg' would be a valid input filename.\n" + << " You only have to provide the filename of one tile as input, heif-enc will scan the directory\n" + << " for the other tiles and determine the range of tiles automatically.\n" + << " --tiled-image-width # override image width of tiled image\n" + << " --tiled-image-height # override image height of tiled image\n" + << " --tiled-input-x-y usually, the first number in the input tile filename should be the y position.\n" + << " With this option, this can be swapped so that the first number is x, the second number y.\n" +#if HEIF_ENABLE_EXPERIMENTAL_FEATURES || WITH_UNCOMPRESSED_CODEC + << " --tiling-method METHOD choose one of these methods: grid" +#if HEIF_ENABLE_EXPERIMENTAL_FEATURES + ", tili (experimental)" +#endif +#if WITH_UNCOMPRESSED_CODEC + ", unci" +#endif + ". The default is 'grid'.\n" +#endif +#if HEIF_ENABLE_EXPERIMENTAL_FEATURES + << " --add-pyramid-group when several images are given, put them into a multi-resolution pyramid group. (experimental)\n" +#endif + << "\n" + << "sequences:\n" + << " -S, --sequence encode input images as sequence (input filenames with a number will pull in all files with this pattern).\n" + << " -V, --video encode as video instead of image sequence\n" + << " --timebase # set clock ticks/second for sequence\n" + << " --duration # set frame duration (default: 1)\n" + << " --fps # set timebase and duration based on fps\n" + << " --repetitions # set how often the sequence should be played back (default=1), special value: 'infinite'\n" + << " --gop-structure GOP frame types to use in GOP (intra-only, low-delay, unrestricted)\n" + << " --min-keyframe-distance # minimum distance of keyframes in sequence (0 = undefined)\n" + << " --max-keyframe-distance # maximum distance of keyframes in sequence (0 = undefined)\n" + << " --max-frames # limit sequence length to maximum number of frames\n" +#if HEIF_ENABLE_EXPERIMENTAL_FEATURES + << " --vmt-metadata FILE encode metadata track from VMT file (experimental)\n" + << " --binary-metadata-track parses VMT data as hex values that are written as raw binary (experimental)\n" + << " --metadata-track-uri URI uses the URI identifier for the metadata track (experimental)\n" + << " --set-gimi-track-id ID set the GIMI track ID for the visual track (experimental)\n" + << " --sai-data-file FILE use the specified FILE as input data for the video frames SAI data\n" +#endif +#if HEIF_WITH_OMAF + << "omnidirectional imagery:\n" + << " --omaf-image-projection PROJ set the image projection (equirectangular, cube-map)\n" +#endif + ; +} + + +void list_encoder_parameters(heif_encoder* encoder) +{ + std::cerr << "Parameters for encoder `" << heif_encoder_get_name(encoder) << "`:\n"; - jpeg_start_decompress(&cinfo); + const heif_encoder_parameter* const* params = heif_encoder_list_parameters(encoder); + for (int i = 0; params[i]; i++) { + const char* name = heif_encoder_parameter_get_name(params[i]); - JSAMPARRAY buffer; - buffer = (*cinfo.mem->alloc_sarray) - ((j_common_ptr) &cinfo, JPOOL_IMAGE, cinfo.output_width * cinfo.output_components, 1); + switch (heif_encoder_parameter_get_type(params[i])) { + case heif_encoder_parameter_type_integer: { + heif_error error; + std::cerr << " " << name; - // create destination image + if (heif_encoder_has_default(encoder, name)) { + int value; + error = heif_encoder_get_parameter_integer(encoder, name, &value); + (void) error; - struct heif_error err = heif_image_create(cinfo.output_width, cinfo.output_height, - heif_colorspace_YCbCr, - heif_chroma_420, - &image); - (void) err; + std::cerr << ", default=" << value; + } - heif_image_add_plane(image, heif_channel_Y, cinfo.output_width, cinfo.output_height, 8); - heif_image_add_plane(image, heif_channel_Cb, (cinfo.output_width + 1) / 2, (cinfo.output_height + 1) / 2, 8); - heif_image_add_plane(image, heif_channel_Cr, (cinfo.output_width + 1) / 2, (cinfo.output_height + 1) / 2, 8); + int have_minimum, have_maximum, minimum, maximum, num_valid_values; + const int* valid_values = nullptr; + error = heif_encoder_parameter_integer_valid_values(encoder, name, + &have_minimum, &have_maximum, + &minimum, &maximum, + &num_valid_values, + &valid_values); - int y_stride; - int cb_stride; - int cr_stride; - uint8_t* py = heif_image_get_plane(image, heif_channel_Y, &y_stride); - uint8_t* pcb = heif_image_get_plane(image, heif_channel_Cb, &cb_stride); - uint8_t* pcr = heif_image_get_plane(image, heif_channel_Cr, &cr_stride); + if (have_minimum || have_maximum) { // TODO: only one is set + std::cerr << ", [" << minimum << ";" << maximum << "]"; + } - // read the image + if (num_valid_values > 0) { + std::cerr << ", {"; - //printf("jpeg size: %d %d\n",cinfo.output_width, cinfo.output_height); + for (int p = 0; p < num_valid_values; p++) { + if (p > 0) { + std::cerr << ", "; + } - while (cinfo.output_scanline < cinfo.output_height) { - JOCTET* bufp; + std::cerr << valid_values[p]; + } - (void) jpeg_read_scanlines(&cinfo, buffer, 1); + std::cerr << "}"; + } - bufp = buffer[0]; + std::cerr << "\n"; + } + break; - int y = cinfo.output_scanline - 1; + case heif_encoder_parameter_type_boolean: { + heif_error error; + std::cerr << " " << name; - for (unsigned int x = 0; x < cinfo.output_width; x += 2) { - py[y * y_stride + x] = *bufp++; - pcb[y / 2 * cb_stride + x / 2] = *bufp++; - pcr[y / 2 * cr_stride + x / 2] = *bufp++; + if (heif_encoder_has_default(encoder, name)) { + int value; + error = heif_encoder_get_parameter_boolean(encoder, name, &value); + (void) error; - if (x + 1 < cinfo.output_width) { - py[y * y_stride + x + 1] = *bufp++; + std::cerr << ", default=" << (value ? "true" : "false"); } - bufp += 2; + std::cerr << "\n"; } + break; + case heif_encoder_parameter_type_string: { + heif_error error; + std::cerr << " " << name; - if (cinfo.output_scanline < cinfo.output_height) { - (void) jpeg_read_scanlines(&cinfo, buffer, 1); + if (heif_encoder_has_default(encoder, name)) { + const int value_size = 50; + char value[value_size]; + error = heif_encoder_get_parameter_string(encoder, name, value, value_size); + (void) error; - bufp = buffer[0]; + std::cerr << ", default=" << value; + } - y = cinfo.output_scanline - 1; + const char* const* valid_options; + error = heif_encoder_parameter_string_valid_values(encoder, name, &valid_options); - for (unsigned int x = 0; x < cinfo.output_width; x++) { - py[y * y_stride + x] = *bufp++; - bufp += 2; + if (valid_options) { + std::cerr << ", { "; + for (int k = 0; valid_options[k]; k++) { + if (k > 0) { std::cerr << ","; } + std::cerr << valid_options[k]; + } + std::cerr << " }"; } + + std::cerr << "\n"; } + break; } } +} - if (embeddedIccFlag && iccLen > 0) { - heif_image_set_raw_color_profile(image, "prof", iccBuffer, (size_t) iccLen); - } - // cleanup - free(iccBuffer); - jpeg_finish_decompress(&cinfo); - jpeg_destroy_decompress(&cinfo); +void set_params(heif_encoder* encoder, const std::vector& params) +{ + for (const std::string& p : params) { + auto pos = p.find_first_of('='); + if (pos == std::string::npos || pos == 0 || pos == p.size() - 1) { + std::cerr << "Encoder parameter must be in the format 'name=value'\n"; + exit(5); + } - fclose(infile); + std::string name = p.substr(0, pos); + std::string value = p.substr(pos + 1); - return std::shared_ptr(image, - [](heif_image* img) { heif_image_release(img); }); + struct heif_error error = heif_encoder_set_parameter(encoder, name.c_str(), value.c_str()); + if (error.code) { + std::cerr << "Error: " << error.message << "\n"; + exit(5); + } + } } -#else -std::shared_ptr loadJPEG(const char* filename) -{ - std::cerr << "Cannot load JPEG because libjpeg support was not compiled.\n"; - exit(1); - - return nullptr; -} -#endif +static void show_list_of_encoders(const heif_encoder_descriptor* const* encoder_descriptors, + int count) +{ + for (int i = 0; i < count; i++) { + std::cout << "- " << heif_encoder_descriptor_get_id_name(encoder_descriptors[i]) + << " = " + << heif_encoder_descriptor_get_name(encoder_descriptors[i]); -#if HAVE_LIBPNG + if (i == 0) { + std::cout << " [default]"; + } -static void -user_read_fn(png_structp png_ptr, png_bytep data, png_size_t length) -{ - FILE* fh = (FILE*) png_get_io_ptr(png_ptr); - size_t n = fread((char*) data, length, 1, fh); - (void) n; -} // user_read_data + std::cout << "\n"; + } +} -std::shared_ptr loadPNG(const char* filename, int output_bit_depth) +static const char* get_compression_format_name(heif_compression_format format) { - FILE* fh = fopen(filename, "rb"); - if (!fh) { - std::cerr << "Can't open " << filename << "\n"; - exit(1); + switch (format) { + case heif_compression_AV1: + return "AV1"; + break; + case heif_compression_AVC: + return "AVC"; + break; + case heif_compression_VVC: + return "VVC"; + break; + case heif_compression_HEVC: + return "HEVC"; + break; + case heif_compression_JPEG: + return "JPEG"; + break; + case heif_compression_JPEG2000: + return "JPEG 2000"; + break; + case heif_compression_HTJ2K: + return "HT-J2K"; + break; + case heif_compression_uncompressed: + return "Uncompressed"; + break; + default: + assert(false); + return "unknown"; } +} +static void show_list_of_all_encoders() +{ + for (auto compression_format: {heif_compression_AVC, heif_compression_AV1, heif_compression_HEVC, + heif_compression_JPEG, heif_compression_JPEG2000, heif_compression_HTJ2K, + heif_compression_uncompressed, heif_compression_VVC + }) { + + switch (compression_format) { + case heif_compression_AVC: + std::cout << "AVC"; + break; + case heif_compression_AV1: + std::cout << "AVIF"; + break; + case heif_compression_HEVC: + std::cout << "HEIC"; + break; + case heif_compression_JPEG: + std::cout << "JPEG"; + break; + case heif_compression_JPEG2000: + std::cout << "JPEG 2000"; + break; + case heif_compression_HTJ2K: + std::cout << "JPEG 2000 (HT)"; + break; + case heif_compression_uncompressed: + std::cout << "Uncompressed"; + break; + case heif_compression_VVC: + std::cout << "VVIC"; + break; + default: + assert(false); + } - // ### Code copied from LibVideoGfx and slightly modified to use HeifPixelImage + std::cout << " encoders:\n"; - struct heif_image* image = nullptr; +#define MAX_ENCODERS 10 + const heif_encoder_descriptor* encoder_descriptors[MAX_ENCODERS]; + int count = heif_get_encoder_descriptors(compression_format, + nullptr, + encoder_descriptors, MAX_ENCODERS); +#undef MAX_ENCODERS - png_structp png_ptr; - png_infop info_ptr; - png_uint_32 width, height; - int bit_depth, color_type, interlace_type; - int compression_type; - png_charp name; -#if (PNG_LIBPNG_VER < 10500) - png_charp png_profile_data; -#else - png_bytep png_profile_data; -#endif - uint8_t* profile_data = nullptr; - png_uint_32 profile_length = 5; - - /* Create and initialize the png_struct with the desired error handler - * functions. If you want to use the default stderr and longjump method, - * you can supply NULL for the last three parameters. We also supply the - * the compiler header file version, so that we know if the application - * was compiled with a compatible version of the library. REQUIRED - */ - png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); - assert(png_ptr != NULL); - - /* Allocate/initialize the memory for image information. REQUIRED. */ - info_ptr = png_create_info_struct(png_ptr); - if (info_ptr == NULL) { - png_destroy_read_struct(&png_ptr, (png_infopp) NULL, (png_infopp) NULL); - assert(false); // , "could not create info_ptr"); - } // if - - /* Set error handling if you are using the setjmp/longjmp method (this is - * the normal method of doing things with libpng). REQUIRED unless you - * set up your own error handlers in the png_create_read_struct() earlier. - */ - if (setjmp(png_jmpbuf(png_ptr))) { - /* Free all of the memory associated with the png_ptr and info_ptr */ - png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL); - /* If we get here, we had a problem reading the file */ - assert(false); // , "fatal error in png library"); - } // if - - /* If you are using replacement read functions, instead of calling - * png_init_io() here you would call: */ - png_set_read_fn(png_ptr, (void*) fh, user_read_fn); - /* where user_io_ptr is a structure you want available to the callbacks */ - - /* The call to png_read_info() gives us all of the information from the - * PNG file before the first IDAT (image data chunk). REQUIRED - */ - png_read_info(png_ptr, info_ptr); - - png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, - &interlace_type, NULL, NULL); - - if (png_get_valid(png_ptr, info_ptr, PNG_INFO_iCCP)) { - if (PNG_INFO_iCCP == - png_get_iCCP(png_ptr, info_ptr, &name, &compression_type, &png_profile_data, &profile_length) && - profile_length > 0) { - profile_data = (uint8_t*) malloc(profile_length); - if (profile_data) { - memcpy(profile_data, png_profile_data, profile_length); - } - } + show_list_of_encoders(encoder_descriptors, count); } - /**** Set up the data transformations you want. Note that these are all - **** optional. Only call them if you want/need them. Many of the - **** transformations only work on specific types of images, and many - **** are mutually exclusive. - ****/ - - // \TODO - // /* Strip alpha bytes from the input data without combining with the - // * background (not recommended). - // */ - // png_set_strip_alpha(png_ptr); - - /* Extract multiple pixels with bit depths of 1, 2, and 4 from a single - * byte into separate bytes (useful for paletted and grayscale images). - */ - png_set_packing(png_ptr); - - - /* Expand paletted colors into true RGB triplets */ - if (color_type == PNG_COLOR_TYPE_PALETTE) - png_set_expand(png_ptr); - - /* Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel */ - if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) - png_set_expand(png_ptr); - - /* Set the background color to draw transparent and alpha images over. - * It is possible to set the red, green, and blue components directly - * for paletted images instead of supplying a palette index. Note that - * even if the PNG file supplies a background, you are not required to - * use it - you should use the (solid) application background if it has one. - */ - -#if 0 - // \TODO 0 is index in color lookup table - correct? used already? - png_color_16 my_background = {0, 255, 255, 255, 255}; - png_color_16 *image_background; - - if (png_get_bKGD(png_ptr, info_ptr, &image_background)) - png_set_background(png_ptr, image_background, PNG_BACKGROUND_GAMMA_FILE, 1, 1.0); - else - png_set_background(png_ptr, &my_background, PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0); -#endif - +} - /* Optional call to gamma correct and add the background to the palette - * and update info structure. REQUIRED if you are expecting libpng to - * update the palette for you (ie you selected such a transform above). - */ - png_read_update_info(png_ptr, info_ptr); - /* Allocate the memory to hold the image using the fields of info_ptr. */ +bool ends_with(const std::string& str, const std::string& end) +{ + if (str.length() < end.length()) { + return false; + } + else { + return str.compare(str.length() - end.length(), end.length(), end) == 0; + } +} - /* The easiest way to read the image: */ - uint8_t** row_pointers = new png_bytep[height]; - assert(row_pointers != NULL); - for (uint32_t y = 0; y < height; y++) { - row_pointers[y] = (png_bytep) malloc(png_get_rowbytes(png_ptr, info_ptr)); - assert(row_pointers[y] != NULL); - } // for - - /* Now it's time to read the image. One of these methods is REQUIRED */ - png_read_image(png_ptr, row_pointers); - - /* read rest of file, and get additional chunks in info_ptr - REQUIRED */ - png_read_end(png_ptr, info_ptr); - - /* clean up after the read, and free any memory allocated - REQUIRED */ - png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL); - - - // OK, now we should have the png image in some way in - // row_pointers, have fun with it - - int band = 0; - switch (color_type) { - case PNG_COLOR_TYPE_GRAY: - case PNG_COLOR_TYPE_GRAY_ALPHA: - band = 1; - break; - case PNG_COLOR_TYPE_PALETTE: - case PNG_COLOR_TYPE_RGB: - case PNG_COLOR_TYPE_RGB_ALPHA: - band = 3; - break; - default: - assert(false); // , "unknown color type in png image."); - } // switch +heif_compression_format guess_compression_format_from_filename(const std::string& filename) +{ + std::string filename_lowercase = filename; + std::transform(filename_lowercase.begin(), filename_lowercase.end(), filename_lowercase.begin(), ::tolower); + if (ends_with(filename_lowercase, ".avif")) { + return heif_compression_AV1; + } + else if (ends_with(filename_lowercase, ".vvic")) { + return heif_compression_VVC; + } + else if (ends_with(filename_lowercase, ".avci")) { + return heif_compression_AVC; + } + else if (ends_with(filename_lowercase, ".heic")) { + return heif_compression_HEVC; + } + else if (ends_with(filename_lowercase, ".hej2")) { + return heif_compression_JPEG2000; + } + else { + return heif_compression_undefined; + } +} +std::string suffix_for_compression_format(heif_compression_format format) +{ + switch (format) { + case heif_compression_AV1: return "avif"; + case heif_compression_VVC: return "vvic"; + case heif_compression_AVC: return "avci"; + case heif_compression_HEVC: return "heic"; + case heif_compression_JPEG2000: return "hej2"; + default: return "data"; + } +} - struct heif_error err; - bool has_alpha = (color_type & PNG_COLOR_MASK_ALPHA); +InputImage load_image(const std::string& input_filename, int output_bit_depth) +{ + InputImage input_image; - if (band == 1 && bit_depth==8) { - err = heif_image_create((int) width, (int) height, - heif_colorspace_monochrome, - heif_chroma_monochrome, - &image); - (void) err; + // get file type from file name - heif_image_add_plane(image, heif_channel_Y, (int) width, (int) height, 8); + std::string suffix; + auto suffix_pos = input_filename.find_last_of('.'); + if (suffix_pos != std::string::npos) { + suffix = input_filename.substr(suffix_pos + 1); + std::transform(suffix.begin(), suffix.end(), suffix.begin(), ::tolower); + } - int y_stride; - int a_stride; - uint8_t* py = heif_image_get_plane(image, heif_channel_Y, &y_stride); - uint8_t* pa = nullptr; + enum + { + PNG, JPEG, Y4M, TIFF, RAW + } filetype = JPEG; + if (suffix == "png") { + filetype = PNG; + } + else if (suffix == "y4m") { + filetype = Y4M; + } + else if (suffix == "tif" || suffix == "tiff") { + filetype = TIFF; + } + else if (suffix == "raw" || suffix == "img") { + filetype = RAW; + } - if (has_alpha) { - heif_image_add_plane(image, heif_channel_Alpha, (int) width, (int) height, 8); + if (force_raw_input) { + filetype = RAW; + } - pa = heif_image_get_plane(image, heif_channel_Alpha, &a_stride); + if (filetype == PNG) { + heif_error err = loadPNG(input_filename.c_str(), output_bit_depth, &input_image); + if (err.code != heif_error_Ok) { + std::cerr << "Can not load PNG input image: " << err.message << '\n'; + exit(1); + } + } + else if (filetype == Y4M) { + heif_error err = loadY4M(input_filename.c_str(), &input_image); + if (err.code != heif_error_Ok) { + std::cerr << "Can not load Y4M input image: " << err.message << '\n'; + exit(1); + } + } +#if HAVE_LIBTIFF + else if (filetype == TIFF) { + heif_error err = loadTIFF(input_filename.c_str(), output_bit_depth, &input_image); + if (err.code != heif_error_Ok) { + std::cerr << "Can not load TIFF input image: " << err.message << '\n'; + exit(1); + } + } +#endif + else if (filetype == RAW) { + heif_error err = loadRAW(input_filename.c_str(), raw_input_params, &input_image); + if (err.code != heif_error_Ok) { + std::cerr << "Can not load raw input image: " << err.message << '\n'; + exit(1); + } + } + else { + heif_error err = loadJPEG(input_filename.c_str(), &input_image); + if (err.code != heif_error_Ok) { + std::cerr << "Can not load JPEG input image: " << err.message << '\n'; + exit(1); } + } + return input_image; +} - for (uint32_t y = 0; y < height; y++) { - uint8_t* p = row_pointers[y]; - if (has_alpha) { - for (uint32_t x = 0; x < width; x++) { - py[y * y_stride + x] = *p++; - pa[y * a_stride + x] = *p++; - } - } - else { - memcpy(&py[y * y_stride], p, width); - } - } +heif_error create_output_nclx_profile_and_configure_encoder(heif_encoder* encoder, + heif_color_profile_nclx** out_nclx, + std::shared_ptr input_image, + bool lossless, + heif_output_nclx_color_profile_preset profile_preset) +{ + *out_nclx = heif_nclx_color_profile_alloc(); + if (!*out_nclx) { + return {heif_error_Encoding_error, heif_suberror_Unspecified, "Cannot allocate NCLX color profile."}; } - else if (band == 1) { - assert(bit_depth>8); - err = heif_image_create((int) width, (int) height, - heif_colorspace_monochrome, - heif_chroma_monochrome, - &image); - (void) err; + heif_color_profile_nclx* nclx = *out_nclx; // abbreviation; - int bdShift = 16 - output_bit_depth; - heif_image_add_plane(image, heif_channel_Y, (int) width, (int) height, output_bit_depth); + // set NCLX based on preset + + switch (profile_preset) { + case heif_output_nclx_color_profile_preset_custom: { + heif_error error = heif_nclx_color_profile_set_matrix_coefficients(nclx, nclx_matrix_coefficients); + if (error.code) { + std::cerr << "Invalid matrix coefficients specified.\n"; + exit(5); + } - int y_stride; - int a_stride = 0; - uint16_t* py = (uint16_t*)heif_image_get_plane(image, heif_channel_Y, &y_stride); - uint16_t* pa = nullptr; + error = heif_nclx_color_profile_set_transfer_characteristics(nclx, nclx_transfer_characteristic); + if (error.code) { + std::cerr << "Invalid transfer characteristics specified.\n"; + exit(5); + } - if (has_alpha) { - heif_image_add_plane(image, heif_channel_Alpha, (int) width, (int) height, output_bit_depth); + error = heif_nclx_color_profile_set_color_primaries(nclx, nclx_colour_primaries); + if (error.code) { + std::cerr << "Invalid color primaries specified.\n"; + exit(5); + } - pa = (uint16_t*)heif_image_get_plane(image, heif_channel_Alpha, &a_stride); + nclx->full_range_flag = (uint8_t) nclx_full_range; + break; } - y_stride /= 2; - a_stride /= 2; + case heif_output_nclx_color_profile_preset_automatic: { + heif_color_profile_nclx* input_nclx = nullptr; - for (uint32_t y = 0; y < height; y++) { - uint8_t* p = row_pointers[y]; + // --- use input image color profile, if it exists - if (has_alpha) { - for (uint32_t x = 0; x < width; x++) { - uint16_t vp = (uint16_t) (((p[0] << 8) | p[1]) >> bdShift); - uint16_t va = (uint16_t) (((p[2] << 8) | p[3]) >> bdShift); + heif_error error = heif_image_get_nclx_color_profile(input_image.get(), &input_nclx); + if (error.code == heif_error_Color_profile_does_not_exist) { - py[x + y * y_stride] = vp; - pa[x + y * y_stride] = va; + // input image has not color profile, guess one - p += 4; + if (heif_image_get_colorspace(input_image.get()) == heif_colorspace_RGB) { + // sRGB + nclx->matrix_coefficients = heif_matrix_coefficients_ITU_R_BT_709_5; // will be overwritten below if lossless + nclx->color_primaries = heif_color_primaries_ITU_R_BT_709_5; + nclx->transfer_characteristics = heif_transfer_characteristic_IEC_61966_2_1; } + else { + // BT.709 + nclx->matrix_coefficients = heif_matrix_coefficients_ITU_R_BT_709_5; // will be overwritten below if lossless + nclx->color_primaries = heif_color_primaries_ITU_R_BT_709_5; + nclx->transfer_characteristics = heif_transfer_characteristic_ITU_R_BT_709_5; + } + } + else if (error.code) { + std::cerr << "Cannot get input NCLX color profile.\n"; + return error; } else { - for (uint32_t x = 0; x < width; x++) { - uint16_t vp = (uint16_t) (((p[0] << 8) | p[1]) >> bdShift); + // no error, we have an input color profile that we can use for output too - py[x + y * y_stride] = vp; + nclx->matrix_coefficients = input_nclx->matrix_coefficients; + nclx->transfer_characteristics = input_nclx->transfer_characteristics; + nclx->color_primaries = input_nclx->color_primaries; + nclx->full_range_flag = input_nclx->full_range_flag; - p += 2; - } + heif_nclx_color_profile_free(input_nclx); + input_nclx = nullptr; } + + assert(!input_nclx); + break; } - } - else if (bit_depth == 8) { - err = heif_image_create((int) width, (int) height, - heif_colorspace_RGB, - has_alpha ? heif_chroma_interleaved_RGBA : heif_chroma_interleaved_RGB, - &image); - (void) err; - heif_image_add_plane(image, heif_channel_interleaved, (int) width, (int) height, - has_alpha ? 32 : 24); + case heif_output_nclx_color_profile_preset_Rec_601: + nclx->matrix_coefficients = heif_matrix_coefficients_ITU_R_BT_601_6; + nclx->color_primaries = heif_color_primaries_ITU_R_BT_601_6; + nclx->transfer_characteristics = heif_transfer_characteristic_ITU_R_BT_601_6; + break; + + case heif_output_nclx_color_profile_preset_compatible: + case heif_output_nclx_color_profile_preset_Rec_709: + nclx->matrix_coefficients = heif_matrix_coefficients_ITU_R_BT_709_5; + nclx->color_primaries = heif_color_primaries_ITU_R_BT_709_5; + nclx->transfer_characteristics = heif_transfer_characteristic_ITU_R_BT_709_5; + break; - int stride; - uint8_t* p = heif_image_get_plane(image, heif_channel_interleaved, &stride); + case heif_output_nclx_color_profile_preset_Rec_2020: + nclx->matrix_coefficients = heif_matrix_coefficients_ITU_R_BT_2020_2_constant_luminance; + nclx->color_primaries = heif_color_primaries_ITU_R_BT_2020_2_and_2100_0; - for (uint32_t y = 0; y < height; y++) { - if (has_alpha) { - memcpy(p + y * stride, row_pointers[y], width * 4); + if (heif_image_has_channel(input_image.get(), heif_channel_Y) && + heif_image_get_bits_per_pixel(input_image.get(), heif_channel_Y) <= 10) { + nclx->transfer_characteristics = heif_transfer_characteristic_ITU_R_BT_2020_2_10bit; } else { - memcpy(p + y * stride, row_pointers[y], width * 3); + nclx->transfer_characteristics = heif_transfer_characteristic_ITU_R_BT_2020_2_12bit; } - } + break; } - else { - err = heif_image_create((int) width, (int) height, - heif_colorspace_RGB, - has_alpha ? - heif_chroma_interleaved_RRGGBBAA_LE : - heif_chroma_interleaved_RRGGBB_LE, - &image); - (void) err; - int bdShift = 16 - output_bit_depth; + // modify NCLX depending on input image - heif_image_add_plane(image, heif_channel_interleaved, (int) width, (int) height, output_bit_depth); + if (lossless) { + heif_encoder_set_lossless(encoder, true); - int stride; - uint8_t* p_out = (uint8_t*) heif_image_get_plane(image, heif_channel_interleaved, &stride); + if (heif_image_get_colorspace(input_image.get()) == heif_colorspace_RGB) { + nclx->matrix_coefficients = heif_matrix_coefficients_RGB_GBR; + nclx->full_range_flag = true; - for (uint32_t y = 0; y < height; y++) { - uint8_t* p = row_pointers[y]; + heif_error error = heif_encoder_set_parameter(encoder, "chroma", "444"); + if (error.code) { + return error; + } + } + else { + heif_error error; - uint32_t nVal = (has_alpha ? 4 : 3) * width; + // TODO: this assumes that the encoder plugin has a 'chroma' parameter. Currently, they do, but there should be a better way to set this. + switch (heif_image_get_chroma_format(input_image.get())) { + case heif_chroma_420: + case heif_chroma_monochrome: + error = heif_encoder_set_parameter(encoder, "chroma", "420"); + break; + case heif_chroma_422: + error = heif_encoder_set_parameter(encoder, "chroma", "422"); + break; + case heif_chroma_444: + error = heif_encoder_set_parameter(encoder, "chroma", "444"); + break; + default: + assert(false); + exit(5); + } - for (uint32_t x = 0; x < nVal; x++) { - uint16_t v = (uint16_t) (((p[0] << 8) | p[1]) >> bdShift); - p_out[2 * x + y * stride + 1] = (uint8_t) (v >> 8); - p_out[2 * x + y * stride + 0] = (uint8_t) (v & 0xFF); - p += 2; + if (error.code) { + return error; + } } - } - } - - if (profile_data && profile_length > 0) { - heif_image_set_raw_color_profile(image, "prof", profile_data, (size_t) profile_length); } - free(profile_data); - for (uint32_t y = 0; y < height; y++) { - free(row_pointers[y]); - } // for - - delete[] row_pointers; - - return std::shared_ptr(image, - [](heif_image* img) { heif_image_release(img); }); + return {heif_error_Ok}; } -#else -std::shared_ptr loadPNG(const char* filename, int output_bit_depth) + +struct input_tiles_generator { - std::cerr << "Cannot load PNG because libpng support was not compiled.\n"; - exit(1); + virtual ~input_tiles_generator() = default; - return nullptr; -} -#endif + virtual uint32_t nColumns() const = 0; + virtual uint32_t nRows() const = 0; + + virtual uint32_t nTiles() const { return nColumns() * nRows(); } + virtual InputImage get_image(uint32_t tx, uint32_t ty, int output_bit_depth) = 0; +}; -std::shared_ptr loadY4M(const char* filename) +struct input_tiles_generator_separate_files : public input_tiles_generator { - struct heif_image* image = nullptr; + uint32_t first_start; + uint32_t first_end; + uint32_t first_digits; + uint32_t second_start; + uint32_t second_end; + uint32_t second_digits; + std::filesystem::path directory; + std::string prefix; + std::string separator; + std::string suffix; - // open input file + bool first_is_x = false; - std::ifstream istr(filename, std::ios_base::binary); - if (istr.fail()) { - std::cerr << "Can't open " << filename << "\n"; - exit(1); - } + uint32_t nColumns() const override { return first_is_x ? (first_end - first_start + 1) : (second_end - second_start + 1); } + uint32_t nRows() const override { return first_is_x ? (second_end - second_start + 1) : (first_end - first_start + 1); } + uint32_t nTiles() const override { return (first_end - first_start + 1) * (second_end - second_start + 1); } - std::string header; - getline(istr, header); + std::filesystem::path filename(uint32_t tx, uint32_t ty) const + { + std::stringstream sstr; - if (header.find("YUV4MPEG2 ") != 0) { - std::cerr << "Input is not a Y4M file.\n"; - exit(1); + sstr << prefix << std::setw(first_digits) << std::setfill('0') << (first_is_x ? tx : ty) + first_start; + sstr << separator << std::setw(second_digits) << std::setfill('0') << (first_is_x ? ty : tx) + second_start; + sstr << suffix; + + std::filesystem::path p = directory / sstr.str(); + return p; } - int w = -1; - int h = -1; + InputImage get_image(uint32_t tx, uint32_t ty, int output_bit_depth) override + { + std::string input_filename = filename(tx, ty).string(); + InputImage image = load_image(input_filename, output_bit_depth); + return image; + } +}; - size_t pos = 0; - for (;;) { - pos = header.find(' ', pos + 1) + 1; - if (pos == std::string::npos) { - break; - } +std::shared_ptr determine_input_images_tiling(const std::string& filename, bool first_is_x) +{ + std::regex pattern(R"((.*\D)?(\d+)(\D+?)(\d+)(\..+)$)"); + std::smatch match; - size_t end = header.find_first_of(" \n", pos + 1); - if (end == std::string::npos) { - break; - } + auto generator = std::make_shared(); - if (end - pos <= 1) { - std::cerr << "Header format error in Y4M file.\n"; - exit(1); - } + if (std::regex_match(filename, match, pattern)) { + std::string prefix = match[1]; - char tag = header[pos]; - std::string value = header.substr(pos + 1, end - pos - 1); - if (tag == 'W') { - w = atoi(value.c_str()); - } - else if (tag == 'H') { - h = atoi(value.c_str()); - } - } + auto p = std::filesystem::absolute(std::filesystem::path(prefix)); + generator->directory = p.parent_path(); + generator->prefix = p.filename().string(); // TODO: we could also use u8string(), but it is not well supported in C++20 - std::string frameheader; - getline(istr, frameheader); + generator->separator = match[3]; + generator->suffix = match[5]; - if (frameheader != "FRAME") { - std::cerr << "Y4M misses the frame header.\n"; - exit(1); - } + generator->first_start = 9999; + generator->first_end = 0; + generator->first_digits = 9; - if (w < 0 || h < 0) { - std::cerr << "Y4M has invalid frame size.\n"; - exit(1); + generator->second_start = 9999; + generator->second_end = 0; + generator->second_digits = 9; + } + else { + return nullptr; } - struct heif_error err = heif_image_create(w, h, - heif_colorspace_YCbCr, - heif_chroma_420, - &image); - (void) err; - // TODO: handle error + std::string patternString = generator->prefix + "(\\d+)" + generator->separator + "(\\d+)" + generator->suffix + "$"; + pattern = patternString; - heif_image_add_plane(image, heif_channel_Y, w, h, 8); - heif_image_add_plane(image, heif_channel_Cb, (w + 1) / 2, (h + 1) / 2, 8); - heif_image_add_plane(image, heif_channel_Cr, (w + 1) / 2, (h + 1) / 2, 8); + for (const auto& dirEntry : std::filesystem::directory_iterator(generator->directory)) + { + if (dirEntry.is_regular_file()) { + std::string s{dirEntry.path().filename().string()}; - int y_stride, cb_stride, cr_stride; - uint8_t* py = heif_image_get_plane(image, heif_channel_Y, &y_stride); - uint8_t* pcb = heif_image_get_plane(image, heif_channel_Cb, &cb_stride); - uint8_t* pcr = heif_image_get_plane(image, heif_channel_Cr, &cr_stride); + if (std::regex_match(s, match, pattern)) { + uint32_t first = std::stoi(match[1]); + uint32_t second = std::stoi(match[2]); - for (int y = 0; y < h; y++) { - istr.read((char*) (py + y * y_stride), w); - } + generator->first_digits = std::min(generator->first_digits, (uint32_t)match[1].length()); + generator->second_digits = std::min(generator->second_digits, (uint32_t)match[2].length()); - for (int y = 0; y < (h + 1) / 2; y++) { - istr.read((char*) (pcb + y * cb_stride), (w + 1) / 2); + generator->first_start = std::min(generator->first_start, first); + generator->first_end = std::max(generator->first_end, first); + generator->second_start = std::min(generator->second_start, second); + generator->second_end = std::max(generator->second_end, second); + } + } } - for (int y = 0; y < (h + 1) / 2; y++) { - istr.read((char*) (pcr + y * cr_stride), (w + 1) / 2); - } + generator->first_is_x = first_is_x; - return std::shared_ptr(image, - [](heif_image* img) { heif_image_release(img); }); + return generator; } -void list_encoder_parameters(heif_encoder* encoder) +class input_tiles_generator_cut_image : public input_tiles_generator { - std::cerr << "Parameters for encoder `" << heif_encoder_get_name(encoder) << "`:\n"; +public: + input_tiles_generator_cut_image(const char* filename, int tile_size, int output_bit_depth) + { + mImage = load_image(filename, output_bit_depth); - const struct heif_encoder_parameter* const* params = heif_encoder_list_parameters(encoder); - for (int i = 0; params[i]; i++) { - const char* name = heif_encoder_parameter_get_name(params[i]); + mWidth = heif_image_get_primary_width(mImage.image.get()); + mHeight = heif_image_get_primary_height(mImage.image.get()); - switch (heif_encoder_parameter_get_type(params[i])) { - case heif_encoder_parameter_type_integer: { - heif_error error; + mTileSize = tile_size; + } - std::cerr << " " << name; + uint32_t nColumns() const override { return (mWidth + mTileSize - 1)/mTileSize; } + uint32_t nRows() const override { return (mHeight + mTileSize - 1)/mTileSize; } + + InputImage get_image(uint32_t tx, uint32_t ty, int output_bit_depth) override + { + heif_image* tileImage; + heif_error err = heif_image_extract_area(mImage.image.get(), tx * mTileSize, ty * mTileSize, mTileSize, mTileSize, + heif_get_global_security_limits(), + &tileImage); + if (err.code) { + std::cerr << "error extracting tile " << tx << ";" << ty << std::endl; + exit(1); + } - if (heif_encoder_has_default(encoder, name)) { - int value; - error = heif_encoder_get_parameter_integer(encoder, name, &value); - (void) error; + InputImage tile; + tile.image = std::shared_ptr(tileImage, + [](heif_image* img) { heif_image_release(img); }); + return tile; + } - std::cerr << ", default=" << value; - } + uint32_t get_image_width() const { return heif_image_get_primary_width(mImage.image.get()); } + uint32_t get_image_height() const { return heif_image_get_primary_height(mImage.image.get()); } - int have_minimum, have_maximum, minimum, maximum, num_valid_values; - const int* valid_values = nullptr; - error = heif_encoder_parameter_integer_valid_values(encoder, name, - &have_minimum, &have_maximum, - &minimum, &maximum, - &num_valid_values, - &valid_values); +private: + InputImage mImage; + uint32_t mWidth, mHeight; + int mTileSize; +}; - if (have_minimum || have_maximum) { // TODO: only one is set - std::cerr << ", [" << minimum << ";" << maximum << "]"; - } - if (num_valid_values > 0) { - std::cerr << ", {"; +#if HAVE_LIBTIFF +class input_tiles_generator_tiff : public input_tiles_generator +{ +public: + input_tiles_generator_tiff(std::shared_ptr reader) + : m_reader(std::move(reader)) + { + } - for (int p=0;p0) { - std::cerr << ", "; - } + uint32_t nColumns() const override { return m_reader->nColumns(); } + uint32_t nRows() const override { return m_reader->nRows(); } - std::cerr << valid_values[p]; - } + InputImage get_image(uint32_t tx, uint32_t ty, int output_bit_depth) override + { + heif_image* tile_image = nullptr; + heif_error err = m_reader->readTile(tx, ty, output_bit_depth, &tile_image); + if (err.code != heif_error_Ok) { + std::cerr << "Error reading TIFF tile " << tx << "," << ty << ": " << err.message << "\n"; + exit(1); + } - std::cerr << "}"; - } + InputImage input; + input.image = std::shared_ptr(tile_image, + [](heif_image* img) { heif_image_release(img); }); + return input; + } - std::cerr << "\n"; - } - break; + uint32_t imageWidth() const { return m_reader->imageWidth(); } + uint32_t imageHeight() const { return m_reader->imageHeight(); } + uint32_t tileWidth() const { return m_reader->tileWidth(); } + uint32_t tileHeight() const { return m_reader->tileHeight(); } - case heif_encoder_parameter_type_boolean: { - heif_error error; - std::cerr << " " << name; + void readExif(InputImage* input_image) { m_reader->readExif(input_image); } - if (heif_encoder_has_default(encoder, name)) { - int value; - error = heif_encoder_get_parameter_boolean(encoder, name, &value); - (void) error; +private: + std::shared_ptr m_reader; +}; +#endif - std::cerr << ", default=" << (value ? "true" : "false"); - } - std::cerr << "\n"; - } - break; +// TODO: we have to attach the input image Exif and XMP to the tiled image +heif_image_handle* encode_tiled(heif_context* ctx, heif_encoder* encoder, heif_encoding_options* options, + int output_bit_depth, + const std::shared_ptr& tile_generator, + const heif_image_tiling& tiling, + [[maybe_unused]] const std::map, std::string>* tile_content_ids = nullptr, + [[maybe_unused]] int layer_index = 0) +{ + heif_image_handle* tiled_image = nullptr; - case heif_encoder_parameter_type_string: { - heif_error error; - std::cerr << " " << name; - if (heif_encoder_has_default(encoder, name)) { - const int value_size = 50; - char value[value_size]; - error = heif_encoder_get_parameter_string(encoder, name, value, value_size); - (void) error; + // --- create the main grid image - std::cerr << ", default=" << value; - } + if (tiling_method == "grid") { + heif_error error = heif_context_add_grid_image(ctx, tiling.image_width, tiling.image_height, + tiling.num_columns, tiling.num_rows, + options, + &tiled_image); + if (error.code != 0) { + std::cerr << "Could not generate grid image: " << error.message << "\n"; + return nullptr; + } + } +#if HEIF_ENABLE_EXPERIMENTAL_FEATURES + else if (tiling_method == "tili") { + heif_tiled_image_parameters tiled_params{}; + tiled_params.version = 1; + tiled_params.image_width = tiling.image_width; + tiled_params.image_height = tiling.image_height; + tiled_params.tile_width = tiling.tile_width; + tiled_params.tile_height = tiling.tile_height; + tiled_params.offset_field_length = 32; + tiled_params.size_field_length = 24; + tiled_params.tiles_are_sequential = 1; + + heif_error error = heif_context_add_tiled_image(ctx, &tiled_params, options, encoder, &tiled_image); + if (error.code != 0) { + std::cerr << "Could not generate tili image: " << error.message << "\n"; + return nullptr; + } + } +#endif +#if WITH_UNCOMPRESSED_CODEC + else if (tiling_method == "unci") { + heif_unci_image_parameters params{}; + params.version = 1; + params.image_width = tiling.image_width; + params.image_height = tiling.image_height; + params.tile_width = tiling.tile_width; + params.tile_height = tiling.tile_height; + params.compression = unci_compression; + + InputImage prototype_image = tile_generator->get_image(0,0, output_bit_depth); + + heif_error error = heif_context_add_empty_unci_image(ctx, ¶ms, options, prototype_image.image.get(), &tiled_image); + if (error.code != 0) { + std::cerr << "Could not generate unci image: " << error.message << "\n"; + return nullptr; + } + } +#endif + else { + assert(false); + exit(10); + } - const char* const* valid_options; - error = heif_encoder_parameter_string_valid_values(encoder, name, &valid_options); - if (valid_options) { - std::cerr << ", { "; - for (int i = 0; valid_options[i]; i++) { - if (i > 0) { std::cerr << ","; } - std::cerr << valid_options[i]; - } - std::cerr << " }"; + // --- add all the image tiles + + std::cout << "encoding tiled image, tile size: " << tiling.tile_width << "x" << tiling.tile_height + << " image size: " << tiling.image_width << "x" << tiling.image_height << "\n"; + + int tile_width = tiling.tile_width; + int tile_height = tiling.tile_height; + + for (uint32_t ty = 0; ty < tile_generator->nRows(); ty++) + for (uint32_t tx = 0; tx < tile_generator->nColumns(); tx++) { + InputImage input_image = tile_generator->get_image(tx,ty, output_bit_depth); + + heif_error error; + error = heif_image_extend_to_size_fill_with_zero(input_image.image.get(), tile_width, tile_height); + if (error.code) { + std::cerr << error.message << "\n"; + } + + std::cout << "encoding tile " << ty+1 << " " << tx+1 + << " (of " << tile_generator->nRows() << "x" << tile_generator->nColumns() << ") \r"; + std::cout.flush(); + +#if HEIF_ENABLE_EXPERIMENTAL_FEATURES + if (tile_content_ids) { + auto it = tile_content_ids->find({layer_index, static_cast(tx), static_cast(ty)}); + if (it != tile_content_ids->end()) { + heif_image_set_gimi_sample_content_id(input_image.image.get(), it->second.c_str()); } + } +#endif - std::cerr << "\n"; + error = heif_context_add_image_tile(ctx, tiled_image, tx, ty, + input_image.image.get(), + encoder); + if (error.code != 0) { + std::cerr << "Could not encode HEIF/AVIF file: " << error.message << "\n"; + return nullptr; } - break; } - } + + std::cout << "\n"; + + return tiled_image; } -void set_params(struct heif_encoder* encoder, std::vector params) +template +std::vector parse_comma_separated_numeric_arguments(std::string arg, + std::vector max_val) { - for (std::string p : params) { - auto pos = p.find_first_of('='); - if (pos == std::string::npos || pos == 0 || pos == p.size() - 1) { - std::cerr << "Encoder parameter must be in the format 'name=value'\n"; - exit(5); + std::istringstream ss(arg); + std::string token; + + std::vector results; + + for (size_t i = 0 ; i max_val[i]) return {}; + results.push_back(static_cast(val)); + } catch (...) { + return {}; } + } - std::string name = p.substr(0, pos); - std::string value = p.substr(pos + 1); + // There should be no extra tokens + if (ss.rdbuf()->in_avail() != 0) return {}; - struct heif_error error = heif_encoder_set_parameter(encoder, name.c_str(), value.c_str()); - if (error.code) { - std::cerr << "Error: " << error.message << "\n"; - exit(5); - } - } + return results; +} + +bool prefix_compare(const char* a, const char* b) +{ + auto minLen = std::min(strlen(a), strlen(b)); + return strncmp(a,b,minLen) == 0; } -static void show_list_of_encoders(const heif_encoder_descriptor*const* encoder_descriptors, - int count) +bool set_metadata_compression_method(const std::string& arg) { - std::cout << "Encoders (first is default):\n"; - for (int i = 0; i < count; i++) { - std::cout << "- " << heif_encoder_descriptor_get_id_name(encoder_descriptors[i]) - << " = " - << heif_encoder_descriptor_get_name(encoder_descriptors[i]) - << "\n"; + if (arg == "auto") { + metadata_compression_method = heif_metadata_compression_auto; + return true; + } + else if (arg == "off") { + metadata_compression_method = heif_metadata_compression_off; + return true; + } + else if (arg == "brotli") { + metadata_compression_method = heif_metadata_compression_brotli; + return true; + } + else if (arg == "deflate") { + metadata_compression_method = heif_metadata_compression_deflate; + return true; + } + else if (arg == "zlib") { + metadata_compression_method = heif_metadata_compression_zlib; + return true; + } + else { + std::cerr << "Unknown metadata compression method '" << arg << "'. Choose between {auto,off,deflate,zlib,brotli}\n"; + return false; } } + +class LibHeifInitializer +{ +public: + LibHeifInitializer() { heif_init(nullptr); } + + ~LibHeifInitializer() { heif_deinit(); } +}; + + +int do_encode_images(heif_context*, heif_encoder*, heif_encoding_options* options, const std::vector& args); +int do_encode_sequence(heif_context*, heif_encoder*, heif_encoding_options* options, std::vector args); +int add_mime_item(heif_context* context); + + int main(int argc, char** argv) { - int quality = 50; - bool lossless = false; - std::string output_filename; - int logging_level = 0; - bool option_show_parameters = false; - int thumbnail_bbox_size = 0; - int output_bit_depth = 10; - bool enc_av1f = false; - bool crop_to_even_size = false; + // This takes care of initializing libheif and also deinitializing it at the end to free all resources. + LibHeifInitializer initializer; std::vector raw_params; while (true) { int option_index = 0; - int c = getopt_long(argc, argv, "hq:Lo:vPp:t:b:AEe:", long_options, &option_index); + int c = getopt_long(argc, argv, "hq:Lo:vPp:t:b:Ae:C:TSV" +#if WITH_UNCOMPRESSED_CODEC + "U" +#endif + , long_options, &option_index); if (c == -1) break; @@ -1031,6 +1493,9 @@ int main(int argc, char** argv) case 'h': show_help(argv[0]); return 0; + case 'v': + heif_examples::show_version(); + return 0; case 'q': quality = atoi(optarg); break; @@ -1040,7 +1505,7 @@ int main(int argc, char** argv) case 'o': output_filename = optarg; break; - case 'v': + case OPTION_VERBOSE: logging_level++; break; case 'P': @@ -1054,36 +1519,403 @@ int main(int argc, char** argv) break; case 'b': output_bit_depth = atoi(optarg); + if (output_bit_depth < 9 || output_bit_depth > 16) { + std::cerr << "Bit depth for input HDR images must be 9-16 bits.\n"; + return 5; + } break; case 'A': - enc_av1f = true; + force_enc_av1f = true; break; - case 'E': - crop_to_even_size = true; +#if WITH_UNCOMPRESSED_CODEC + case 'U': + force_enc_uncompressed = true; break; +#endif case 'e': encoderId = optarg; break; case OPTION_NCLX_MATRIX_COEFFICIENTS: - nclx_matrix_coefficients = atoi(optarg); + nclx_matrix_coefficients = (uint16_t) strtoul(optarg, nullptr, 0); break; case OPTION_NCLX_COLOUR_PRIMARIES: - nclx_colour_primaries = atoi(optarg); + nclx_colour_primaries = (uint16_t) strtoul(optarg, nullptr, 0); break; case OPTION_NCLX_TRANSFER_CHARACTERISTIC: - nclx_transfer_characteristic = atoi(optarg); + nclx_transfer_characteristic = (uint16_t) strtoul(optarg, nullptr, 0); break; case OPTION_NCLX_FULL_RANGE_FLAG: nclx_full_range = atoi(optarg); break; - } - } + case OPTION_PITM_DESCRIPTION: + property_pitm_description = optarg; + break; + case OPTION_USE_HEVC_COMPRESSION: + force_enc_hevc = true; + break; + case OPTION_USE_VVC_COMPRESSION: + force_enc_vvc = true; + break; + case OPTION_USE_AVC_COMPRESSION: + force_enc_avc = true; + break; + case OPTION_USE_JPEG_COMPRESSION: + force_enc_jpeg = true; + break; + case OPTION_USE_JPEG2000_COMPRESSION: + force_enc_jpeg2000 = true; + break; + case OPTION_USE_HTJ2K_COMPRESSION: + force_enc_htj2k = true; + break; + case OPTION_PLUGIN_DIRECTORY: { + int nPlugins; + heif_error error = heif_load_plugins(optarg, nullptr, &nPlugins, 0); + if (error.code) { + std::cerr << "Error loading libheif plugins: " << error.message << "\n"; + return 1; + } + + // Note: since we process the option within the loop, we can only consider the '-v' flags coming before the plugin loading option. + if (logging_level > 0) { + std::cout << nPlugins << " plugins loaded from directory " << optarg << "\n"; + } + break; + } + case OPTION_TILED_IMAGE_WIDTH: + tiled_image_width = (int) strtol(optarg, nullptr, 0); + break; + case OPTION_TILED_IMAGE_HEIGHT: + tiled_image_height = (int) strtol(optarg, nullptr, 0); + break; + case OPTION_TILING_METHOD: + tiling_method = optarg; + if (tiling_method != "grid" +#if WITH_UNCOMPRESSED_CODEC + && tiling_method != "unci" +#endif +#if HEIF_ENABLE_EXPERIMENTAL_FEATURES + && tiling_method != "tili" +#endif + ) { + std::cerr << "Invalid tiling method '" << tiling_method << "'\n"; + exit(5); + } + break; + case OPTION_CUT_TILES: + cut_tiles = atoi(optarg); + break; + case OPTION_UNCI_COMPRESSION: { + std::string option(optarg); + if (option == "none") { + unci_compression = heif_unci_compression_off; + } + else if (option == "brotli") { + unci_compression = heif_unci_compression_brotli; + } + else if (option == "deflate") { + unci_compression = heif_unci_compression_deflate; + } + else if (option == "zlib") { + unci_compression = heif_unci_compression_zlib; + } + else { + std::cerr << "Invalid unci compression method '" << option << "'\n"; + exit(5); + } + break; + } + case OPTION_COMPONENT_CONTENT_IDS: + option_component_content_ids = true; + break; + case 'C': + chroma_downsampling = optarg; + if (chroma_downsampling != "nn" && + chroma_downsampling != "nearest-neighbor" && + chroma_downsampling != "average" && + chroma_downsampling != "sharp-yuv") { + fprintf(stderr, "Undefined chroma downsampling algorithm.\n"); + exit(5); + } + if (chroma_downsampling == "nn") { // abbreviation + chroma_downsampling = "nearest-neighbor"; + } +#if !HAVE_LIBSHARPYUV + if (chroma_downsampling == "sharp-yuv") { + std::cerr << "Error: sharp-yuv chroma downsampling method has not been compiled into libheif.\n"; + return 5; + } +#endif + break; + case 'T': + use_tiling = true; + break; + case 'S': + encode_sequence = true; + break; + case 'V': + use_video_handler = true; + break; + case OPTION_SEQUENCES_TIMEBASE: + sequence_timebase = atoi(optarg); + break; + case OPTION_SEQUENCES_DURATIONS: + sequence_durations = atoi(optarg); + break; + case OPTION_SEQUENCES_FPS: + if (strcmp(optarg,"29.97")==0) { + sequence_durations = 1001; + sequence_timebase = 30000; + } + else { + double fps = std::atof(optarg); + sequence_timebase = 90000; + sequence_durations = (uint32_t)(90000 / fps + 0.5); + } + break; + case OPTION_SEQUENCES_REPETITIONS: + if (strcmp(optarg, "infinite")==0) { + sequence_repetitions = heif_sequence_maximum_number_of_repetitions; + } + else { + sequence_repetitions = atoi(optarg); + if (sequence_repetitions == 0) { + std::cerr << "Sequence repetitions may not be 0.\n"; + return 5; + } + } + break; + case OPTION_SEQUENCES_GOP_STRUCTURE: + if (prefix_compare(optarg, "intra-only")) { + sequence_gop_structure = heif_sequence_gop_structure_intra_only; + } + else if (prefix_compare(optarg, "low-delay") || prefix_compare(optarg, "p")) { + sequence_gop_structure = heif_sequence_gop_structure_lowdelay; + } + else if (prefix_compare(optarg, "unrestricted") || prefix_compare(optarg, "b")) { + sequence_gop_structure = heif_sequence_gop_structure_unrestricted; + } + else { + std::cerr << "Invalid GOP structure argument\n"; + return 5; + } + break; + case OPTION_SEQUENCES_MIN_KEYFRAME_DISTANCE: + sequence_keyframe_distance_min = atoi(optarg); + if (sequence_keyframe_distance_min < 0) { + std::cerr << "Keyframe distance must be >= 0\n"; + return 5; + } + break; + case OPTION_SEQUENCES_MAX_KEYFRAME_DISTANCE: + sequence_keyframe_distance_max = atoi(optarg); + if (sequence_keyframe_distance_max < 0) { + std::cerr << "Keyframe distance must be >= 0\n"; + return 5; + } + break; + case OPTION_SEQUENCES_MAX_FRAMES: + sequence_max_frames = atoi(optarg); + if (sequence_max_frames <= 0) { + std::cerr << "Maximum number of frames must be >= 1\n"; + return 5; + } + break; + case OPTION_COLOR_PROFILE_PRESET: + if (strcmp(optarg, "auto")==0) { + output_color_profile_preset = heif_output_nclx_color_profile_preset_automatic; + } + else if (strcmp(optarg, "custom")==0) { + output_color_profile_preset = heif_output_nclx_color_profile_preset_custom; + } + else if (strcmp(optarg, "compatible")==0) { + output_color_profile_preset = heif_output_nclx_color_profile_preset_compatible; + } + else if (strcmp(optarg, "601")==0) { + output_color_profile_preset = heif_output_nclx_color_profile_preset_Rec_601; + } + else if (strcmp(optarg, "709")==0) { + output_color_profile_preset = heif_output_nclx_color_profile_preset_Rec_709; + } + else if (strcmp(optarg, "2020")==0) { + output_color_profile_preset = heif_output_nclx_color_profile_preset_Rec_2020; + } + else { + std::cerr << "Invalid color-profile preset.\n"; + return 5; + } + break; + case OPTION_VMT_METADATA_FILE: + vmt_metadata_file = optarg; + break; + case OPTION_BINARY_METADATA_TRACK: + binary_metadata_track = true; + break; + case OPTION_METADATA_TRACK_URI: + metadata_track_uri = optarg; + break; + case OPTION_SET_CLLI: { + auto clli_args = parse_comma_separated_numeric_arguments(optarg, + { + std::numeric_limits::max(), + std::numeric_limits::max() + }); + if (clli_args.empty()) { + std::cerr << "Invalid arguments for --clli option.\n"; + } + else { + heif_content_light_level clliVal; + clliVal.max_content_light_level = clli_args[0]; + clliVal.max_pic_average_light_level = clli_args[1]; + clli = clliVal; + } + break; + } + case OPTION_SET_PASP: { + auto pasp_args = parse_comma_separated_numeric_arguments(optarg, + { + std::numeric_limits::max(), + std::numeric_limits::max() + }); + if (pasp_args.empty()) { + std::cerr << "Invalid arguments for --pasp option.\n"; + } + else { + pixel_aspect_ratio aspect_ratio; + aspect_ratio.h = pasp_args[0]; + aspect_ratio.v = pasp_args[1]; + pasp = aspect_ratio; + } + break; + } + case OPTION_DO_ROTATE: { + auto angle = parse_int(optarg); + if (!angle || (*angle != 0 && *angle != 90 && *angle != 180 && *angle != 270)) { + std::cerr << "Invalid rotation angle. Must be 0, 90, 180, or 270.\n"; + return 5; + } + if (*angle == 90) { + transform = heif_orientation_concat(transform, heif_orientation_rotate_90_cw); + } + else if (*angle == 180) { + transform = heif_orientation_concat(transform, heif_orientation_rotate_180); + } + else if (*angle == 270) { + transform = heif_orientation_concat(transform, heif_orientation_rotate_270_cw); + } + break; + } + case OPTION_DO_FLIP_H: + transform = heif_orientation_concat(transform, heif_orientation_flip_horizontally); + break; + case OPTION_DO_FLIP_V: + transform = heif_orientation_concat(transform, heif_orientation_flip_vertically); + break; + case OPTION_ADD_MIME_ITEM: + option_mime_item_type = optarg; + break; + case OPTION_MIME_ITEM_FILE: + option_mime_item_file = optarg; + break; + case OPTION_MIME_ITEM_NAME: + option_mime_item_name = optarg; + break; +#if HEIF_ENABLE_EXPERIMENTAL_FEATURES + case OPTION_TURTLE: + option_turtle_file = optarg; + break; + case OPTION_EMBED_TURTLE: + option_embed_turtle = true; + break; +#endif + case OPTION_METADATA_COMPRESSION: { + bool success = set_metadata_compression_method(optarg); + if (!success) { + exit(5); + } + break; + } + case OPTION_SEQUENCES_GIMI_TRACK_ID: + option_gimi_track_id = optarg; + break; + case OPTION_SEQUENCES_SAI_DATA_FILE: + option_sai_data_file = optarg; + break; +#if HEIF_WITH_OMAF + case OPTION_SET_OMAF_IMAGE_PROJECTION: + if (strcmp(optarg, "equirectangular") == 0) { + omaf_image_projection = heif_omaf_image_projection_equirectangular; + } else if (strcmp(optarg, "cube-map") == 0) { + omaf_image_projection = heif_omaf_image_projection_cube_map; + } else { + std::cerr << "image projection must be 'equirectangular' or 'cube-map'\n"; + return 5; + } + break; +#endif + case OPTION_ADD_COMPATIBLE_BRAND: + if (strlen(optarg) != 4) { + std::cerr << "Brand must be exactly 4 characters\n"; + return 5; + } + additional_compatible_brands.push_back(heif_fourcc_to_brand(optarg)); + break; + case OPTION_UNIF: + option_unif = true; + break; + case OPTION_RAW: + force_raw_input = true; + break; + case OPTION_RAW_WIDTH: + raw_input_params.width = atoi(optarg); + break; + case OPTION_RAW_HEIGHT: + raw_input_params.height = atoi(optarg); + break; + case OPTION_RAW_TYPE: + if (!parse_raw_pixel_type(optarg, &raw_input_params.datatype, &raw_input_params.bit_depth)) { + std::cerr << "Unknown raw pixel type: " << optarg << "\n"; + exit(5); + } + break; + case OPTION_RAW_ENDIAN: + raw_input_params.big_endian = (std::string(optarg) == "big"); + break; + } + } if (quality < 0 || quality > 100) { std::cerr << "Invalid quality factor. Must be between 0 and 100.\n"; return 5; } + if ((force_enc_av1f ? 1 : 0) + (force_enc_vvc ? 1 : 0) + (force_enc_uncompressed ? 1 : 0) + (force_enc_jpeg ? 1 : 0) + + (force_enc_jpeg2000 ? 1 : 0) + (force_enc_avc ? 1 : 0) + (force_enc_hevc ? 1 : 0) > 1) { + std::cerr << "Choose at most one output compression format.\n"; + return 5; + } + + if (encode_sequence && (use_tiling || cut_tiles)) { + std::cerr << "Image sequences cannot be used together with tiling.\n"; + return 5; + } + + if (sequence_timebase <= 0) { + std::cerr << "Sequence clock tick rate cannot be zero.\n"; + return 5; + } + + if (sequence_durations <= 0) { + std::cerr << "Sequence frame durations cannot be zero.\n"; + return 5; + } + + + if (!option_sai_data_file.empty() && !encode_sequence) { + std::cerr << "Image SAI data can only be used with sequences.\n"; + return 5; + } + if (logging_level > 0) { logging_level += 2; @@ -1093,9 +1925,54 @@ int main(int argc, char** argv) } - // ============================================================================== + struct heif_encoder* encoder = nullptr; + + if (list_encoders) { + show_list_of_all_encoders(); + return 0; + } + + // --- determine output compression format (from output filename or command line parameter) + + heif_compression_format compressionFormat; + + if (force_enc_av1f) { + compressionFormat = heif_compression_AV1; + } + else if (force_enc_vvc) { + compressionFormat = heif_compression_VVC; + } + else if (force_enc_avc) { + compressionFormat = heif_compression_AVC; + } + else if (force_enc_uncompressed) { + compressionFormat = heif_compression_uncompressed; + } + else if (force_enc_jpeg) { + compressionFormat = heif_compression_JPEG; + } + else if (force_enc_jpeg2000) { + compressionFormat = heif_compression_JPEG2000; + } + else if (force_enc_htj2k) { + compressionFormat = heif_compression_HTJ2K; + } + else if (force_enc_hevc) { + compressionFormat = heif_compression_HEVC; + } + else { + compressionFormat = guess_compression_format_from_filename(output_filename); + } + + if (compressionFormat == heif_compression_undefined) { + compressionFormat = heif_compression_HEVC; + } + + + // --- select encoder + std::shared_ptr context(heif_context_alloc(), [](heif_context* c) { heif_context_free(c); }); if (!context) { @@ -1103,27 +1980,24 @@ int main(int argc, char** argv) return 1; } + if (option_unif) { + heif_context_set_unif(context.get(), 1); + } - struct heif_encoder* encoder = nullptr; -#define MAX_ENCODERS 5 +#define MAX_ENCODERS 10 const heif_encoder_descriptor* encoder_descriptors[MAX_ENCODERS]; - int count = heif_context_get_encoder_descriptors(context.get(), - enc_av1f ? heif_compression_AV1 : heif_compression_HEVC, - nullptr, - encoder_descriptors, MAX_ENCODERS); - - if (list_encoders) { - show_list_of_encoders(encoder_descriptors, count); - return 0; - } + int count = heif_get_encoder_descriptors(compressionFormat, + nullptr, + encoder_descriptors, MAX_ENCODERS); +#undef MAX_ENCODERS const heif_encoder_descriptor* active_encoder_descriptor = nullptr; if (count > 0) { int idx = 0; if (encoderId != nullptr) { for (int i = 0; i <= count; i++) { - if (i==count) { + if (i == count) { std::cerr << "Unknown encoder ID. Choose one from the list below.\n"; show_list_of_encoders(encoder_descriptors, count); return 5; @@ -1145,139 +2019,481 @@ int main(int argc, char** argv) active_encoder_descriptor = encoder_descriptors[idx]; } else { - std::cerr << "No " << (enc_av1f ? "AV1" : "HEVC") << " encoder available.\n"; + std::cerr << "No " << get_compression_format_name(compressionFormat) << " encoder available.\n"; return 5; } - if (option_show_parameters) { list_encoder_parameters(encoder); + heif_encoder_release(encoder); return 0; } - if (optind > argc - 1) { show_help(argv[0]); return 0; } - struct heif_error error; + if (lossless && !heif_encoder_descriptor_supports_lossless_compression(active_encoder_descriptor)) { + std::cerr << "Warning: the selected encoder does not support lossless encoding. Encoding in lossy mode.\n"; + lossless = false; + } + + // If we were given a list of filenames and no '-o' option, check whether the last filename is the desired output filename. + + if (output_filename.empty() && argc>1) { + if (guess_compression_format_from_filename(argv[argc-1]) != heif_compression_undefined) { + output_filename = argv[argc-1]; + argc--; + } + } + std::vector args; for (; optind < argc; optind++) { - std::string input_filename = argv[optind]; + args.emplace_back(argv[optind]); + } - if (output_filename.empty()) { - std::string filename_without_suffix; - std::string::size_type dot_position = input_filename.find_last_of('.'); - if (dot_position != std::string::npos) { - filename_without_suffix = input_filename.substr(0, dot_position); - } - else { - filename_without_suffix = input_filename; - } - output_filename = filename_without_suffix + (enc_av1f ? ".avif" : ".heic"); + // If we get a list of image filenames, but no '-o' option, assume that the last option + // denotes the output filename. + + if (output_filename.empty() && args.size() > 1) { + output_filename = args.back(); + args.pop_back(); + } + + + if (!lossless) { + heif_encoder_set_lossy_quality(encoder, quality); + } + + heif_encoder_set_logging_level(encoder, logging_level); + + set_params(encoder, raw_params); + struct heif_encoding_options* options = heif_encoding_options_alloc(); + options->save_two_colr_boxes_when_ICC_and_nclx_available = (uint8_t) two_colr_boxes; + + if (chroma_downsampling == "average") { + options->color_conversion_options.preferred_chroma_downsampling_algorithm = heif_chroma_downsampling_average; + options->color_conversion_options.only_use_preferred_chroma_algorithm = true; + } + else if (chroma_downsampling == "sharp-yuv") { + options->color_conversion_options.preferred_chroma_downsampling_algorithm = heif_chroma_downsampling_sharp_yuv; + options->color_conversion_options.only_use_preferred_chroma_algorithm = true; + } + else if (chroma_downsampling == "nearest-neighbor") { + options->color_conversion_options.preferred_chroma_downsampling_algorithm = heif_chroma_downsampling_nearest_neighbor; + options->color_conversion_options.only_use_preferred_chroma_algorithm = true; + } + + options->unci_compression = unci_compression; + + // --- if no output filename was given, synthesize one from the first input image filename + + if (output_filename.empty()) { + const std::string& first_input_filename = args[0]; + + std::string filename_without_suffix; + std::string::size_type dot_position = first_input_filename.find_last_of('.'); + if (dot_position != std::string::npos) { + filename_without_suffix = first_input_filename.substr(0, dot_position); } + else { + filename_without_suffix = first_input_filename; + } + + std::string suffix = suffix_for_compression_format(compressionFormat); + output_filename = filename_without_suffix + '.' + suffix; + } + + + int ret; + + if (!encode_sequence) { + ret = do_encode_images(context.get(), encoder, options, args); + } + else { + ret = do_encode_sequence(context.get(), encoder, options, args); + } + + if (ret != 0) { + heif_encoding_options_free(options); + heif_encoder_release(encoder); + return ret; + } + + // --- add extra MIME item with user data + ret = add_mime_item(context.get()); + if (ret != 0) { + heif_encoding_options_free(options); + heif_encoder_release(encoder); + return ret; + } + + + // --- write HEIF file + + for (heif_brand2 brand : additional_compatible_brands) { + heif_context_add_compatible_brand(context.get(), brand); + } + + heif_error error = heif_context_write_to_file(context.get(), output_filename.c_str()); + if (error.code) { + std::cerr << error.message << "\n"; + return 5; + } - // ============================================================================== + heif_encoding_options_free(options); + heif_encoder_release(encoder); + + return 0; +} - // get file type from file name - std::string suffix; - auto suffix_pos = input_filename.find_last_of('.'); - if (suffix_pos != std::string::npos) { - suffix = input_filename.substr(suffix_pos + 1); - std::transform(suffix.begin(), suffix.end(), suffix.begin(), ::tolower); +int add_mime_item(heif_context* context) +{ + if (!option_mime_item_file.empty() || !option_mime_item_type.empty()) { + if (option_mime_item_file.empty() || option_mime_item_type.empty()) { + std::cerr << "Options --add-mime-item and --mime-item-file have to be used together\n"; + return 5; } - enum - { - PNG, JPEG, Y4M - } filetype = JPEG; - if (suffix == "png") { - filetype = PNG; + std::ifstream istr(option_mime_item_file.c_str(), std::ios::binary | std::ios::ate); + if (!istr) { + std::cerr << "Failed to open file for MIME item: '" << option_mime_item_file << "'\n"; + return 5; } - else if (suffix == "y4m") { - filetype = Y4M; + + // Get size by seeking to the end (thanks to ios::ate) + std::streamsize size = istr.tellg(); + if (size < 0) { + std::cerr << "Querying size of file '" << option_mime_item_file << "' failed.\n"; + return 5; } - std::shared_ptr image; - if (filetype == PNG) { - image = loadPNG(input_filename.c_str(), output_bit_depth); + std::vector buffer(size); + + // Seek back to beginning and read + istr.seekg(0, std::ios::beg); + istr.read(reinterpret_cast(buffer.data()), size); + + heif_item_id itemId; + heif_context_add_mime_item(context, option_mime_item_type.c_str(), + metadata_compression_method, + buffer.data(), (int)buffer.size(), + &itemId); + + if (!option_mime_item_name.empty()) { + heif_item_set_item_name(context, itemId, option_mime_item_name.c_str()); } - else if (filetype == Y4M) { - image = loadY4M(input_filename.c_str()); + } + +#if HEIF_ENABLE_EXPERIMENTAL_FEATURES + // --- attach Turtle (RDF) file as MIME item + + if (option_embed_turtle && !option_turtle_file.empty()) { + std::ifstream istr(option_turtle_file.c_str(), std::ios::binary | std::ios::ate); + if (!istr) { + std::cerr << "Failed to open turtle file: '" << option_turtle_file << "'\n"; + return 5; } - else { - image = loadJPEG(input_filename.c_str()); + + std::streamsize size = istr.tellg(); + if (size < 0) { + std::cerr << "Querying size of turtle file '" << option_turtle_file << "' failed.\n"; + return 5; } - heif_color_profile_nclx nclx; - nclx.matrix_coefficients = (heif_matrix_coefficients) nclx_matrix_coefficients; - nclx.transfer_characteristics = (heif_transfer_characteristics) nclx_transfer_characteristic; - nclx.color_primaries = (heif_color_primaries) nclx_colour_primaries; - nclx.full_range_flag = (uint8_t) nclx_full_range; + std::vector buffer(size); + istr.seekg(0, std::ios::beg); + istr.read(reinterpret_cast(buffer.data()), size); + + heif_item_id itemId; + heif_context_add_mime_item(context, "text/turtle", + heif_metadata_compression_off, + buffer.data(), (int)buffer.size(), + &itemId); + } +#endif + + return 0; +} + + +int do_encode_images(heif_context* context, heif_encoder* encoder, heif_encoding_options* options, const std::vector& args) +{ + std::shared_ptr primary_image; + + bool is_primary_image = true; + + std::vector encoded_image_ids; + + for (std::string input_filename : args) { - //heif_image_set_nclx_color_profile(image.get(), &nclx); + InputImage input_image; + heif_image_tiling tiling{}; + std::shared_ptr tile_generator; + std::shared_ptr tiff_reader_for_pyramid; - if (lossless) { - if (heif_encoder_descriptor_supportes_lossless_compression(active_encoder_descriptor)) { - heif_encoder_set_lossless(encoder, lossless); +#if HAVE_LIBTIFF + // Auto-detect tiled TIFFs when not using explicit tiling options + if (!use_tiling && cut_tiles == 0) { + std::string suffix; + auto suffix_pos = input_filename.find_last_of('.'); + if (suffix_pos != std::string::npos) { + suffix = input_filename.substr(suffix_pos + 1); + std::transform(suffix.begin(), suffix.end(), suffix.begin(), ::tolower); } - else { - std::cerr << "Warning: the selected encoder does not support lossless encoding. Encoding in lossy mode.\n"; + + if (suffix == "tif" || suffix == "tiff") { + heif_error tiff_err; + auto tiff_reader = TiledTiffReader::open(input_filename.c_str(), &tiff_err); + if (tiff_err.code != heif_error_Ok) { + std::cerr << "Error opening TIFF: " << tiff_err.message << "\n"; + return 1; + } + + if (tiff_reader) { + auto shared_tiff_reader = std::shared_ptr(std::move(tiff_reader)); + auto tiff_gen = std::make_shared(shared_tiff_reader); + + // Read tile (0,0) as representative for nclx profile + input_image = tiff_gen->get_image(0, 0, output_bit_depth); + tiff_gen->readExif(&input_image); + + tiling.version = 1; + tiling.num_columns = tiff_gen->nColumns(); + tiling.num_rows = tiff_gen->nRows(); + tiling.tile_width = tiff_gen->tileWidth(); + tiling.tile_height = tiff_gen->tileHeight(); + tiling.image_width = tiff_gen->imageWidth(); + tiling.image_height = tiff_gen->imageHeight(); + tiling.number_of_extra_dimensions = 0; + + tile_generator = tiff_gen; + tiff_reader_for_pyramid = shared_tiff_reader; + } } } +#endif - heif_encoder_set_lossy_quality(encoder, quality); - heif_encoder_set_logging_level(encoder, logging_level); + // If no tiled TIFF was detected, load the image normally + if (!input_image.image) { + input_image = load_image(input_filename, output_bit_depth); + } - set_params(encoder, raw_params); + std::shared_ptr image = input_image.image; - struct heif_encoding_options* options = heif_encoding_options_alloc(); - options->save_alpha_channel = (uint8_t) master_alpha; - options->save_two_colr_boxes_when_ICC_and_nclx_available = (uint8_t)two_colr_boxes; - options->output_nclx_profile = &nclx; + if (use_tiling) { + tile_generator = determine_input_images_tiling(input_filename, tiled_input_x_y); + if (tile_generator) { + tiling.version = 1; - if (crop_to_even_size) { - if (heif_image_get_primary_width(image.get()) == 1 || - heif_image_get_primary_height(image.get()) == 1) { - std::cerr << "Image only has a size of 1 pixel width or height. Cannot crop to even size.\n"; - return 1; + tiling.num_columns = tile_generator->nColumns(); + tiling.num_rows = tile_generator->nRows(); + tiling.tile_width = heif_image_get_primary_width(image.get()); + tiling.tile_height = heif_image_get_primary_height(image.get()); + tiling.image_width = tiling.num_columns * tiling.tile_width; + tiling.image_height = tiling.num_rows * tiling.tile_height; + tiling.number_of_extra_dimensions = 0; } - std::cerr << "Warning: option --even-size/-E is deprecated as it is not needed anymore.\n"; - - int right = heif_image_get_primary_width(image.get()) % 2; - int bottom = heif_image_get_primary_height(image.get()) % 2; + if (tiled_image_width) tiling.image_width = tiled_image_width; + if (tiled_image_height) tiling.image_height = tiled_image_height; - error = heif_image_crop(image.get(), 0, right, 0, bottom); - if (error.code != 0) { - heif_encoding_options_free(options); - std::cerr << "Could not crop image: " << error.message << "\n"; - return 1; + if (!tile_generator || tile_generator->nTiles()==1) { + std::cerr << "Cannot enumerate input tiles. Please use filenames with the two tile coordinates in the name.\n"; + return 5; } } + else if (cut_tiles != 0) { + auto cutting_tile_generator = std::make_shared(input_filename.c_str(), + cut_tiles, output_bit_depth); + tile_generator = cutting_tile_generator; + + tiling.num_columns = tile_generator->nColumns(); + tiling.num_rows = tile_generator->nRows(); + tiling.tile_width = cut_tiles; + tiling.tile_height = cut_tiles; + tiling.image_width = cutting_tile_generator->get_image_width(); + tiling.image_height = cutting_tile_generator->get_image_height(); + tiling.number_of_extra_dimensions = 0; + } + + if (!primary_image) { + primary_image = image; + } + +#if HAVE_GETTIMEOFDAY + if (run_benchmark) { + gettimeofday(&time_encoding_start, nullptr); + } +#endif + + heif_color_profile_nclx* nclx; + heif_error error = create_output_nclx_profile_and_configure_encoder(encoder, &nclx, primary_image, + lossless, output_color_profile_preset); + if (error.code) { + std::cerr << error.message << "\n"; + return 5; + } + + options->save_alpha_channel = (uint8_t) master_alpha; + options->output_nclx_profile = nclx; + options->image_orientation = heif_orientation_concat(input_image.orientation, transform); if (premultiplied_alpha) { heif_image_set_premultiplied_alpha(image.get(), premultiplied_alpha); } + heif_image_handle* handle; - struct heif_image_handle* handle; - error = heif_context_encode_image(context.get(), - image.get(), - encoder, - options, - &handle); - if (error.code != 0) { - heif_encoding_options_free(options); - std::cerr << "Could not encode HEIF/AVIF file: " << error.message << "\n"; +#if HEIF_ENABLE_EXPERIMENTAL_FEATURES + TurtleContentIDs turtle_ids; + if (!option_turtle_file.empty()) { + turtle_ids = parse_turtle_content_ids(option_turtle_file); + } +#endif + + if (tile_generator) { +#if HEIF_ENABLE_EXPERIMENTAL_FEATURES + handle = encode_tiled(context, encoder, options, output_bit_depth, tile_generator, tiling, + option_turtle_file.empty() ? nullptr : &turtle_ids.tile_content_ids, 0); +#else + handle = encode_tiled(context, encoder, options, output_bit_depth, tile_generator, tiling); +#endif + } + else { + error = heif_context_encode_image(context, + image.get(), + encoder, + options, + &handle); + if (error.code != 0) { + heif_nclx_color_profile_free(nclx); + std::cerr << "Could not encode HEIF/AVIF file: " << error.message << "\n"; + return 1; + } + } + + if (handle==nullptr) { + std::cerr << "Could not encode image\n"; return 1; } + if (clli) { + heif_image_handle_set_content_light_level(handle, &*clli); + } + + if (pasp) { + heif_image_handle_set_pixel_aspect_ratio(handle, pasp->h, pasp->v); + } + + +#if HEIF_ENABLE_EXPERIMENTAL_FEATURES + if (!option_turtle_file.empty() && !turtle_ids.image_content_id.empty()) { + heif_image_handle_set_gimi_content_id(handle, turtle_ids.image_content_id.c_str()); + } +#endif + +#if HEIF_WITH_OMAF + if (omaf_image_projection) { + heif_image_handle_set_omaf_image_projection(handle, *omaf_image_projection); + } +#endif + + if (option_component_content_ids && force_enc_uncompressed) { + uint32_t num_components = heif_image_handle_get_number_of_cmpd_components(handle); + for (uint32_t i = 0; i < num_components; i++) { + std::string uuid = generate_random_uuid_urn(); + heif_image_handle_set_gimi_component_content_id(handle, i, uuid.c_str()); + } + } + + if (is_primary_image) { + heif_context_set_primary_image(context, handle); + } + + encoded_image_ids.push_back(heif_image_handle_get_item_id(handle)); + +#if HEIF_ENABLE_EXPERIMENTAL_FEATURES && HAVE_LIBTIFF + // Auto-encode TIFF pyramid overviews + if (tiff_reader_for_pyramid && !tiff_reader_for_pyramid->overviews().empty()) { + heif_item_id fullres_id = heif_image_handle_get_item_id(handle); + std::vector pyramid_ids; + pyramid_ids.push_back(fullres_id); + + int ov_layer_index = 1; + for (const auto& ov : tiff_reader_for_pyramid->overviews()) { + if (!tiff_reader_for_pyramid->setDirectory(ov.dir_index)) { + std::cerr << "Warning: could not switch to TIFF overview directory " << ov.dir_index << "\n"; + continue; + } + + auto ov_gen = std::make_shared(tiff_reader_for_pyramid); + + heif_image_tiling ov_tiling{}; + ov_tiling.version = 1; + ov_tiling.num_columns = ov_gen->nColumns(); + ov_tiling.num_rows = ov_gen->nRows(); + ov_tiling.tile_width = ov_gen->tileWidth(); + ov_tiling.tile_height = ov_gen->tileHeight(); + ov_tiling.image_width = ov_gen->imageWidth(); + ov_tiling.image_height = ov_gen->imageHeight(); + ov_tiling.number_of_extra_dimensions = 0; + + heif_image_handle* ov_handle = encode_tiled(context, encoder, options, output_bit_depth, ov_gen, ov_tiling, + option_turtle_file.empty() ? nullptr : &turtle_ids.tile_content_ids, + ov_layer_index); + if (ov_handle) { + pyramid_ids.push_back(heif_image_handle_get_item_id(ov_handle)); + heif_image_handle_release(ov_handle); + } + ov_layer_index++; + } + + // Restore directory 0 for any subsequent use + tiff_reader_for_pyramid->setDirectory(0); + + if (pyramid_ids.size() > 1) { + error = heif_context_add_pyramid_entity_group(context, pyramid_ids.data(), pyramid_ids.size(), nullptr); + if (error.code) { + std::cerr << "Cannot create pyramid entity group: " << error.message << "\n"; + return 5; + } + std::cout << "Created pyramid entity group with " << pyramid_ids.size() << " layers\n"; + } + } +#endif + + // write EXIF to HEIC + if (!input_image.exif.empty()) { + // Note: we do not modify the EXIF Orientation here because we want it to match the HEIF transforms. + // TODO: is this a good choice? Or should we set it to 1 (normal) so that other, faulty software will not transform it once more? + + error = heif_context_add_exif_metadata(context, handle, + input_image.exif.data(), (int) input_image.exif.size()); + if (error.code != 0) { + heif_nclx_color_profile_free(nclx); + std::cerr << "Could not write EXIF metadata: " << error.message << "\n"; + return 1; + } + } + + // write XMP to HEIC + if (!input_image.xmp.empty()) { + error = heif_context_add_XMP_metadata2(context, handle, + input_image.xmp.data(), (int) input_image.xmp.size(), + metadata_compression_method); + if (error.code != 0) { + heif_nclx_color_profile_free(nclx); + std::cerr << "Could not write XMP metadata: " << error.message << "\n"; + return 1; + } + } + if (thumbnail_bbox_size > 0) { // encode thumbnail @@ -1285,7 +2501,7 @@ int main(int argc, char** argv) options->save_alpha_channel = master_alpha && thumb_alpha; - error = heif_context_encode_thumbnail(context.get(), + error = heif_context_encode_thumbnail(context, image.get(), handle, encoder, @@ -1293,7 +2509,7 @@ int main(int argc, char** argv) thumbnail_bbox_size, &thumbnail_handle); if (error.code) { - heif_encoding_options_free(options); + heif_nclx_color_profile_free(nclx); std::cerr << "Could not generate thumbnail: " << error.message << "\n"; return 5; } @@ -1303,17 +2519,299 @@ int main(int argc, char** argv) } } +#if HAVE_GETTIMEOFDAY + if (run_benchmark) { + gettimeofday(&time_encoding_end, nullptr); + } +#endif + heif_image_handle_release(handle); - heif_encoding_options_free(options); + heif_nclx_color_profile_free(nclx); + + is_primary_image = false; } - heif_encoder_release(encoder); + if (!property_pitm_description.empty()) { + heif_image_handle* primary_image_handle; + struct heif_error err = heif_context_get_primary_image_handle(context, &primary_image_handle); + if (err.code) { + std::cerr << "No primary image set, cannot set user description\n"; + return 5; + } + + heif_item_id pitm_id = heif_image_handle_get_item_id(primary_image_handle); + + heif_property_user_description udes; + udes.lang = nullptr; + udes.name = nullptr; + udes.tags = nullptr; + udes.description = property_pitm_description.c_str(); + err = heif_item_add_property_user_description(context, pitm_id, &udes, nullptr); + if (err.code) { + std::cerr << "Cannot set user description\n"; + return 5; + } + + heif_image_handle_release(primary_image_handle); + } + +#if HEIF_ENABLE_EXPERIMENTAL_FEATURES + if (add_pyramid_group && encoded_image_ids.size() > 1) { + heif_error error = heif_context_add_pyramid_entity_group(context, encoded_image_ids.data(), encoded_image_ids.size(), nullptr); + if (error.code) { + std::cerr << "Cannot set multi-resolution pyramid: " << error.message << "\n"; + return 5; + } + } +#endif + + if (run_benchmark) { + double psnr = compute_psnr(primary_image.get(), output_filename); + std::cout << "PSNR: " << std::setprecision(2) << std::fixed << psnr << " "; + +#if HAVE_GETTIMEOFDAY + double t = (double) (time_encoding_end.tv_sec - time_encoding_start.tv_sec) + (double) (time_encoding_end.tv_usec - time_encoding_start.tv_usec) / 1000000.0; + std::cout << "time: " << std::setprecision(1) << std::fixed << t << " "; +#endif + + std::ifstream istr(output_filename.c_str()); + istr.seekg(0, std::ios_base::end); + std::streamoff size = istr.tellg(); + std::cout << "size: " << size << "\n"; + } + + return 0; +} + + + + +std::vector deflate_input_filenames(const std::string& filename_example) +{ + std::regex pattern(R"((.*\D)?(\d+)(\..+)$)"); + std::smatch match; + + if (!std::regex_match(filename_example, match, pattern)) { + return {filename_example}; + } + + std::string prefix = match[1]; + + auto p = std::filesystem::absolute(std::filesystem::path(prefix)); + std::filesystem::path directory = p.parent_path(); + std::string filename_prefix = p.filename().string(); // TODO: we could also use u8string(), but it is not well supported in C++20 + std::string number = match[2]; + std::string suffix = match[3]; + + + std::string patternString = filename_prefix + "(\\d+)" + suffix + "$"; + pattern = patternString; + + uint32_t digits = std::numeric_limits::max(); + uint32_t start = std::numeric_limits::max(); + uint32_t end = 0; + + for (const auto& dirEntry : std::filesystem::directory_iterator(directory)) + { + if (dirEntry.is_regular_file()) { + std::string s{dirEntry.path().filename().string()}; + + if (std::regex_match(s, match, pattern)) { + digits = std::min(digits, (uint32_t)match[1].length()); + + uint32_t number = std::stoi(match[1]); + start = std::min(start, number); + end = std::max(end, number); + } + } + } + + + std::vector files; + + for (uint32_t i=start;i<=end;i++) + { + std::stringstream sstr; + + sstr << prefix << std::setw(digits) << std::setfill('0') << i << suffix; + + std::filesystem::path p = directory / sstr.str(); + files.emplace_back(p.string()); + } + + return files; +} + + +int do_encode_sequence(heif_context* context, heif_encoder* encoder, heif_encoding_options* options, std::vector args) +{ + if (args.size() == 1) { + args = deflate_input_filenames(args[0]); + } + + size_t currImage = 0; + + size_t nImages = args.size(); + if (sequence_max_frames && nImages > static_cast(sequence_max_frames)) { + nImages = sequence_max_frames; + } + + // --- optionally load SAI data to be used for the frames + + SAI_datafile sai_data; + if (!option_sai_data_file.empty()) { + sai_data.load_sai_data_from_file(option_sai_data_file.c_str()); + } + + + uint16_t image_width=0, image_height=0; + + bool first_image = true; + + heif_track* track = nullptr; + heif_sequence_encoding_options* encoding_options = nullptr; + + for (std::string input_filename : args) { + currImage++; + if (currImage > nImages) { + break; + } + + std::cout << "\rencoding sequence image " << currImage << "/" << nImages; + std::cout.flush(); + + InputImage input_image = load_image(input_filename, output_bit_depth); + + std::shared_ptr image = input_image.image; - error = heif_context_write_to_file(context.get(), output_filename.c_str()); + int w = heif_image_get_primary_width(image.get()); + int h = heif_image_get_primary_height(image.get()); + + if (w > 0xFFFF || h > 0xFFFF) { + std::cerr << "maximum image size of 65535x65535 exceeded\n"; + return 5; + } + + if (first_image) { + heif_track_options* track_options = heif_track_options_alloc(); + heif_track_options_set_timescale(track_options, sequence_timebase); + + if (!option_gimi_track_id.empty()) { + heif_track_options_set_gimi_track_id(track_options, option_gimi_track_id.c_str()); + } + + if (sai_data.tai_clock_info) { + heif_track_options_enable_sample_tai_timestamps(track_options, + sai_data.tai_clock_info, + heif_sample_aux_info_presence_optional); + } + + if (!sai_data.gimi_content_ids.empty()) { + heif_track_options_enable_sample_gimi_content_ids(track_options, + heif_sample_aux_info_presence_optional); + } + + heif_context_set_sequence_timescale(context, sequence_timebase); + heif_context_set_number_of_sequence_repetitions(context, sequence_repetitions); + + encoding_options = heif_sequence_encoding_options_alloc(); + encoding_options->gop_structure = sequence_gop_structure; + encoding_options->keyframe_distance_min = sequence_keyframe_distance_min; + encoding_options->keyframe_distance_max = sequence_keyframe_distance_max; + encoding_options->save_alpha_channel = master_alpha; + + image_width = static_cast(w); + image_height = static_cast(h); + + heif_context_add_visual_sequence_track(context, + image_width, image_height, + use_video_handler ? heif_track_type_video : heif_track_type_image_sequence, + track_options, + encoding_options, + &track); + + heif_track_options_release(track_options); + + first_image = false; + } + + if (image_width != static_cast(w) || + image_height != static_cast(h)) { + std::cerr << "image '" << input_filename << "' has size " << w << "x" << h + << " which is different from the first image size " << image_width << "x" << image_height << "\n"; + return 5; + } + + heif_color_profile_nclx* nclx; + heif_error error = create_output_nclx_profile_and_configure_encoder(encoder, &nclx, image, lossless, output_color_profile_preset); + if (error.code) { + std::cerr << error.message << "\n"; + return 5; + } + + //seq_options->save_alpha_channel = false; // TODO: sequences with alpha ? + encoding_options->output_nclx_profile = nclx; + //seq_options->image_orientation = heif_orientation_normal; // input_image.orientation; TODO: sequence rotation + + heif_image_set_duration(image.get(), sequence_durations); + + // --- set SAI data + + if (currImage-1 < sai_data.gimi_content_ids.size()) { + if (!sai_data.gimi_content_ids[currImage-1].empty()) { + heif_image_set_gimi_sample_content_id(image.get(), sai_data.gimi_content_ids[currImage-1].c_str()); + } + } + + if (currImage-1 < sai_data.tai_timestamps.size()) { + if (sai_data.tai_timestamps[currImage-1]) { + heif_image_set_tai_timestamp(image.get(), sai_data.tai_timestamps[currImage-1]); + } + } + + // --- encode image + + error = heif_track_encode_sequence_image(track, image.get(), encoder, encoding_options); + if (error.code) { + heif_nclx_color_profile_free(nclx); + std::cerr << "Cannot encode sequence image: " << error.message << "\n"; + return 5; + } + + heif_nclx_color_profile_free(nclx); + } + + if (!track) { + std::cerr << "No input files could be encoded into sequence track\n"; + return 5; + } + + std::cout << "\n"; + + heif_error error = heif_track_encode_end_of_sequence(track, encoder); if (error.code) { - std::cerr << error.message << "\n"; + std::cerr << "Cannot end sequence: " << error.message << "\n"; return 5; } +#if HEIF_ENABLE_EXPERIMENTAL_FEATURES + if (!vmt_metadata_file.empty()) { + int ret = encode_vmt_metadata_track(context, track, vmt_metadata_file, metadata_track_uri, binary_metadata_track); + if (ret) { + return ret; + } + } +#endif + + heif_track_release(track); + heif_sequence_encoding_options_release(encoding_options); + + + // --- add first image as image item + + if (!use_video_handler) { + do_encode_images(context, encoder, options, {args[0]}); + } + return 0; } diff --git a/examples/heif_gen_bayer.cc b/examples/heif_gen_bayer.cc new file mode 100644 index 0000000000..c59f1b81c2 --- /dev/null +++ b/examples/heif_gen_bayer.cc @@ -0,0 +1,649 @@ +/* + libheif example application "heif-gen-bayer". + + MIT License + + Copyright (c) 2026 Dirk Farin + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "heifio/decoder_png.h" + + +struct PatternDefinition +{ + std::string name; + uint16_t width; + uint16_t height; + std::vector cpat; +}; + + +static const PatternDefinition patterns[] = { + // RGGB (standard Bayer) + // R G + // G B + { + "rggb", 2, 2, + { + heif_uncompressed_component_type_red, + heif_uncompressed_component_type_green, + heif_uncompressed_component_type_green, + heif_uncompressed_component_type_blue, + } + }, + + // RGBW (Red-Green-Blue-White) — 4×4 + // W G W R + // G W B W + // W B W G + // R W G W + // White is an unfiltered (panchromatic) pixel → Y component type. + { + "rgbw", 4, 4, + { + heif_uncompressed_component_type_Y, + heif_uncompressed_component_type_green, + heif_uncompressed_component_type_Y, + heif_uncompressed_component_type_red, + + heif_uncompressed_component_type_green, + heif_uncompressed_component_type_Y, + heif_uncompressed_component_type_blue, + heif_uncompressed_component_type_Y, + + heif_uncompressed_component_type_Y, + heif_uncompressed_component_type_blue, + heif_uncompressed_component_type_Y, + heif_uncompressed_component_type_green, + + heif_uncompressed_component_type_red, + heif_uncompressed_component_type_Y, + heif_uncompressed_component_type_green, + heif_uncompressed_component_type_Y, + } + }, + + // QBC (Quad Bayer Coding) — 4×4 + // G G R R + // G G R R + // B B G G + // B B G G + { + "qbc", 4, 4, + { + heif_uncompressed_component_type_green, + heif_uncompressed_component_type_green, + heif_uncompressed_component_type_red, + heif_uncompressed_component_type_red, + + heif_uncompressed_component_type_green, + heif_uncompressed_component_type_green, + heif_uncompressed_component_type_red, + heif_uncompressed_component_type_red, + + heif_uncompressed_component_type_blue, + heif_uncompressed_component_type_blue, + heif_uncompressed_component_type_green, + heif_uncompressed_component_type_green, + + heif_uncompressed_component_type_blue, + heif_uncompressed_component_type_blue, + heif_uncompressed_component_type_green, + heif_uncompressed_component_type_green, + } + }, +}; + +static constexpr int num_patterns = sizeof(patterns) / sizeof(patterns[0]); + + +static const PatternDefinition* find_pattern(const char* name) +{ + for (int i = 0; i < num_patterns; i++) { + if (strcasecmp(patterns[i].name.c_str(), name) == 0) { + return &patterns[i]; + } + } + return nullptr; +} + + +static std::optional parse_pattern_string(const char* str) +{ + std::string s(str); + size_t len = s.size(); + if (len != 4 && len != 16) { + return {}; + } + + uint16_t dim = (len == 4) ? 2 : 4; + std::vector cpat; + cpat.reserve(len); + + for (char c : s) { + switch (std::tolower(c)) { + case 'r': cpat.push_back(heif_uncompressed_component_type_red); break; + case 'g': cpat.push_back(heif_uncompressed_component_type_green); break; + case 'b': cpat.push_back(heif_uncompressed_component_type_blue); break; + default: return {}; + } + } + + return PatternDefinition{str, dim, dim, std::move(cpat)}; +} + + +static std::vector deflate_input_filenames(const std::string& filename_example) +{ + std::regex pattern(R"((.*\D)?(\d+)(\..+)$)"); + std::smatch match; + + if (!std::regex_match(filename_example, match, pattern)) { + return {filename_example}; + } + + std::string prefix = match[1]; + + auto p = std::filesystem::absolute(std::filesystem::path(prefix)); + std::filesystem::path directory = p.parent_path(); + std::string filename_prefix = p.filename().string(); + std::string number = match[2]; + std::string suffix = match[3]; + + std::string patternString = filename_prefix + "(\\d+)" + suffix + "$"; + pattern = patternString; + + uint32_t digits = std::numeric_limits::max(); + uint32_t start = std::numeric_limits::max(); + uint32_t end = 0; + + for (const auto& dirEntry : std::filesystem::directory_iterator(directory)) + { + if (dirEntry.is_regular_file()) { + std::string s{dirEntry.path().filename().string()}; + + if (std::regex_match(s, match, pattern)) { + digits = std::min(digits, (uint32_t)match[1].length()); + + uint32_t number = std::stoi(match[1]); + start = std::min(start, number); + end = std::max(end, number); + } + } + } + + std::vector files; + + for (uint32_t i = start; i <= end; i++) + { + std::stringstream sstr; + + sstr << prefix << std::setw(digits) << std::setfill('0') << i << suffix; + + std::filesystem::path p = directory / sstr.str(); + files.emplace_back(p.string()); + } + + return files; +} + + +static void print_usage() +{ + std::cerr << "Usage: heif-gen-bayer [options] \n" + << " heif-gen-bayer -S [options] \n\n" + << "Options:\n" + << " -h, --help show this help\n" + << " -b, --bit-depth # output bit depth (default: 8, range: 8-16)\n" + << " -p, --pattern filter array pattern (default: rggb)\n" + << " -S, --sequence sequence mode (expand numbered PNGs)\n" + << " -V, --video use video track handler (vide) instead of pict\n" + << " --fps frames per second (default: 30)\n\n" + << "Patterns:\n"; + for (int i = 0; i < num_patterns; i++) { + std::cerr << " " << patterns[i].name + << " (" << patterns[i].width << "x" << patterns[i].height << ")" + << (i == 0 ? " [default]" : "") + << "\n"; + } + std::cerr << " Or specify a custom R/G/B string of length 4 (2x2) or 16 (4x4), e.g. -p BGGR\n"; +} + + +static struct option long_options[] = { + {(char* const) "help", no_argument, nullptr, 'h'}, + {(char* const) "bit-depth", required_argument, nullptr, 'b'}, + {(char* const) "pattern", required_argument, nullptr, 'p'}, + {(char* const) "sequence", no_argument, nullptr, 'S'}, + {(char* const) "video", no_argument, nullptr, 'V'}, + {(char* const) "fps", required_argument, nullptr, 'f'}, + {nullptr, 0, nullptr, 0} +}; + + +// Create a bayer image from a PNG file. Returns nullptr on error. +// If expected_width/expected_height are non-zero, the PNG must match those dimensions. +static heif_image* create_bayer_image_from_png(const char* png_filename, + const PatternDefinition* pat, + int output_bit_depth, + int expected_width, + int expected_height) +{ + InputImage input_image; + heif_error err = loadPNG(png_filename, output_bit_depth, &input_image); + if (err.code != heif_error_Ok) { + std::cerr << "Cannot load PNG '" << png_filename << "': " << err.message << "\n"; + return nullptr; + } + + heif_image* src_img = input_image.image.get(); + + int width = heif_image_get_primary_width(src_img); + int height = heif_image_get_primary_height(src_img); + + if (expected_width != 0 && (width != expected_width || height != expected_height)) { + std::cerr << "Frame '" << png_filename << "' has dimensions " << width << "x" << height + << " but expected " << expected_width << "x" << expected_height << "\n"; + return nullptr; + } + + if (width % pat->width != 0 || height % pat->height != 0) { + std::cerr << "Image dimensions must be multiples of the pattern size (" + << pat->width << "x" << pat->height << "). Got " + << width << "x" << height << "\n"; + return nullptr; + } + + // Get source RGB data + int src_stride; + const uint8_t* src_data = heif_image_get_plane_readonly(src_img, heif_channel_interleaved, &src_stride); + if (!src_data) { + std::cerr << "Failed to get interleaved RGB plane from PNG.\n"; + return nullptr; + } + + // Create Bayer image + heif_image* bayer_img = nullptr; + err = heif_image_create(width, height, + heif_colorspace_filter_array, + heif_chroma_monochrome, + &bayer_img); + if (err.code != heif_error_Ok) { + std::cerr << "Cannot create image: " << err.message << "\n"; + return nullptr; + } + + err = heif_image_add_plane(bayer_img, heif_channel_filter_array, width, height, output_bit_depth); + if (err.code != heif_error_Ok) { + std::cerr << "Cannot add plane: " << err.message << "\n"; + heif_image_release(bayer_img); + return nullptr; + } + + // Convert RGB to filter array using the selected pattern + if (output_bit_depth == 8) { + int dst_stride; + uint8_t* dst_data = heif_image_get_plane(bayer_img, heif_channel_filter_array, &dst_stride); + + for (int y = 0; y < height; y++) { + const uint8_t* src_row = src_data + y * src_stride; + uint8_t* dst_row = dst_data + y * dst_stride; + + for (int x = 0; x < width; x++) { + uint8_t r = src_row[x * 3 + 0]; + uint8_t g = src_row[x * 3 + 1]; + uint8_t b = src_row[x * 3 + 2]; + + int px = x % pat->width; + int py = y % pat->height; + auto comp_type = pat->cpat[py * pat->width + px]; + + switch (comp_type) { + case heif_uncompressed_component_type_red: dst_row[x] = r; break; + case heif_uncompressed_component_type_green: dst_row[x] = g; break; + case heif_uncompressed_component_type_blue: dst_row[x] = b; break; + case heif_uncompressed_component_type_Y: dst_row[x] = static_cast((r + g + b) / 3); break; + default: + assert(false); + } + } + } + } + else { + int dst_stride; + uint8_t* dst_raw = heif_image_get_plane(bayer_img, heif_channel_filter_array, &dst_stride); + auto* dst_data = reinterpret_cast(dst_raw); + int dst_stride16 = dst_stride / 2; + + for (int y = 0; y < height; y++) { + const uint8_t* src_row = src_data + y * src_stride; + uint16_t* dst_row = dst_data + y * dst_stride16; + + for (int x = 0; x < width; x++) { + // Source is little-endian uint16_t per component, 3 components per pixel + uint16_t r = src_row[x * 6 + 0] | (src_row[x * 6 + 1] << 8); + uint16_t g = src_row[x * 6 + 2] | (src_row[x * 6 + 3] << 8); + uint16_t b = src_row[x * 6 + 4] | (src_row[x * 6 + 5] << 8); + + int px = x % pat->width; + int py = y % pat->height; + auto comp_type = pat->cpat[py * pat->width + px]; + + switch (comp_type) { + case heif_uncompressed_component_type_red: dst_row[x] = r; break; + case heif_uncompressed_component_type_green: dst_row[x] = g; break; + case heif_uncompressed_component_type_blue: dst_row[x] = b; break; + case heif_uncompressed_component_type_Y: dst_row[x] = static_cast((r + g + b) / 3); break; + default: + assert(false); + } + } + } + } + + // Build heif_bayer_pattern_pixel array from component types. + // The component_index values here are the component types themselves — the encoder + // will resolve them to proper cmpd indices when writing the cpat box. + std::vector bayer_pixels(pat->cpat.size()); + for (size_t i = 0; i < pat->cpat.size(); i++) { + bayer_pixels[i].component_index = static_cast(pat->cpat[i]); + bayer_pixels[i].component_gain = 1.0f; + } + + // Set Bayer pattern metadata + err = heif_image_set_bayer_pattern(bayer_img, + pat->width, pat->height, + bayer_pixels.data()); + if (err.code != heif_error_Ok) { + std::cerr << "Cannot set Bayer pattern: " << err.message << "\n"; + heif_image_release(bayer_img); + return nullptr; + } + + return bayer_img; +} + + +static int encode_sequence(const std::vector& filenames, + const PatternDefinition* pat, + int output_bit_depth, + int fps, + bool use_video_handler, + const char* output_filename) +{ + heif_context* ctx = heif_context_alloc(); + + heif_encoder* encoder = nullptr; + heif_error err = heif_context_get_encoder_for_format(ctx, heif_compression_uncompressed, &encoder); + if (err.code != heif_error_Ok) { + std::cerr << "Cannot get uncompressed encoder: " << err.message << "\n"; + heif_context_free(ctx); + return 1; + } + + heif_context_set_sequence_timescale(ctx, fps); + + heif_sequence_encoding_options* enc_options = heif_sequence_encoding_options_alloc(); + heif_track* track = nullptr; + int first_width = 0, first_height = 0; + + for (size_t i = 0; i < filenames.size(); i++) { + heif_image* bayer_img = create_bayer_image_from_png(filenames[i].c_str(), pat, + output_bit_depth, + first_width, first_height); + if (!bayer_img) { + heif_sequence_encoding_options_release(enc_options); + if (track) heif_track_release(track); + heif_encoder_release(encoder); + heif_context_free(ctx); + return 1; + } + + if (i == 0) { + first_width = heif_image_get_primary_width(bayer_img); + first_height = heif_image_get_primary_height(bayer_img); + + heif_track_type track_type = use_video_handler ? heif_track_type_video + : heif_track_type_image_sequence; + + heif_track_options* track_options = heif_track_options_alloc(); + heif_track_options_set_timescale(track_options, fps); + + err = heif_context_add_visual_sequence_track(ctx, + static_cast(first_width), + static_cast(first_height), + track_type, + track_options, + enc_options, + &track); + heif_track_options_release(track_options); + + if (err.code != heif_error_Ok) { + std::cerr << "Cannot create sequence track: " << err.message << "\n"; + heif_image_release(bayer_img); + heif_sequence_encoding_options_release(enc_options); + heif_encoder_release(encoder); + heif_context_free(ctx); + return 1; + } + } + + heif_image_set_duration(bayer_img, 1); + + err = heif_track_encode_sequence_image(track, bayer_img, encoder, enc_options); + heif_image_release(bayer_img); + + if (err.code != heif_error_Ok) { + std::cerr << "Cannot encode frame " << i << ": " << err.message << "\n"; + heif_sequence_encoding_options_release(enc_options); + heif_track_release(track); + heif_encoder_release(encoder); + heif_context_free(ctx); + return 1; + } + + std::cout << "Encoded frame " << (i + 1) << "/" << filenames.size() + << ": " << filenames[i] << "\n"; + } + + err = heif_track_encode_end_of_sequence(track, encoder); + if (err.code != heif_error_Ok) { + std::cerr << "Cannot end sequence: " << err.message << "\n"; + } + + heif_sequence_encoding_options_release(enc_options); + heif_track_release(track); + heif_encoder_release(encoder); + + err = heif_context_write_to_file(ctx, output_filename); + if (err.code != heif_error_Ok) { + std::cerr << "Cannot write file: " << err.message << "\n"; + heif_context_free(ctx); + return 1; + } + + heif_context_free(ctx); + + std::cout << "Wrote " << filenames.size() << " frame(s) to " << output_filename << "\n"; + return 0; +} + + +int main(int argc, char* argv[]) +{ + PatternDefinition custom_pattern; + const PatternDefinition* pat = &patterns[0]; // default: RGGB + int output_bit_depth = 8; + bool sequence_mode = false; + bool use_video_handler = false; + int fps = 30; + + while (true) { + int option_index = 0; + int c = getopt_long(argc, argv, "hb:p:SV", long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + print_usage(); + return 0; + + case 'b': + output_bit_depth = std::atoi(optarg); + if (output_bit_depth < 8 || output_bit_depth > 16) { + std::cerr << "Invalid bit depth: " << optarg << " (must be 8-16)\n"; + return 1; + } + break; + + case 'p': + pat = find_pattern(optarg); + if (!pat) { + auto custom = parse_pattern_string(optarg); + if (custom) { + custom_pattern = std::move(*custom); + pat = &custom_pattern; + } + else { + std::cerr << "Unknown pattern: " << optarg << "\n"; + print_usage(); + return 1; + } + } + break; + + case 'S': + sequence_mode = true; + break; + + case 'V': + use_video_handler = true; + break; + + case 'f': // --fps + fps = std::atoi(optarg); + if (fps <= 0) { + std::cerr << "Invalid FPS value: " << optarg << "\n"; + return 1; + } + break; + + default: + print_usage(); + return 1; + } + } + + if (argc - optind != 2) { + print_usage(); + return 1; + } + + const char* input_filename = argv[optind]; + const char* output_filename = argv[optind + 1]; + + if (sequence_mode) { + // --- Sequence mode: expand numbered filenames and encode as sequence + + std::vector filenames = deflate_input_filenames(input_filename); + if (filenames.empty()) { + std::cerr << "No input files found matching pattern: " << input_filename << "\n"; + return 1; + } + + std::cout << "Found " << filenames.size() << " frame(s), encoding at " << fps << " fps\n"; + return encode_sequence(filenames, pat, output_bit_depth, fps, use_video_handler, output_filename); + } + + // --- Single image mode + + heif_image* bayer_img = create_bayer_image_from_png(input_filename, pat, output_bit_depth, 0, 0); + if (!bayer_img) { + return 1; + } + + // --- Encode + + heif_context* ctx = heif_context_alloc(); + + heif_encoder* encoder = nullptr; + heif_error err = heif_context_get_encoder_for_format(ctx, heif_compression_uncompressed, &encoder); + if (err.code != heif_error_Ok) { + std::cerr << "Cannot get uncompressed encoder: " << err.message << "\n"; + heif_image_release(bayer_img); + heif_context_free(ctx); + return 1; + } + + heif_encoding_options* options = heif_encoding_options_alloc(); + + heif_image_handle* handle = nullptr; + err = heif_context_encode_image(ctx, bayer_img, encoder, options, &handle); + if (err.code != heif_error_Ok) { + std::cerr << "Cannot encode image: " << err.message << "\n"; + heif_encoding_options_free(options); + heif_encoder_release(encoder); + heif_image_release(bayer_img); + heif_context_free(ctx); + return 1; + } + + heif_encoding_options_free(options); + heif_encoder_release(encoder); + heif_image_handle_release(handle); + heif_image_release(bayer_img); + + // --- Write file + + err = heif_context_write_to_file(ctx, output_filename); + if (err.code != heif_error_Ok) { + std::cerr << "Cannot write file: " << err.message << "\n"; + heif_context_free(ctx); + return 1; + } + + heif_context_free(ctx); + + std::cout << "Wrote " << pat->name << " (" + << pat->width << "x" << pat->height + << ") Bayer image to " << output_filename << "\n"; + + return 0; +} diff --git a/examples/heif_info.cc b/examples/heif_info.cc index 56c55b86fb..d893f56289 100644 --- a/examples/heif_info.cc +++ b/examples/heif_info.cc @@ -3,7 +3,7 @@ MIT License - Copyright (c) 2017 struktur AG, Dirk Farin + Copyright (c) 2017 Dirk Farin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -23,9 +23,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#if defined(HAVE_CONFIG_H) -#include "config.h" -#endif +#include #include #include @@ -40,12 +38,25 @@ #endif #include +#include +#include +#include +#include +#include "libheif/heif_sequences.h" +#include +#include +#include #include #include +#include #include #include -#include +#include +#include +#include +#include "common.h" +#include "libheif/heif_items.h" /* @@ -63,40 +74,57 @@ info -w 20012 -o out.265 *file info -d // dump */ -static struct option long_options[] = { +int option_disable_limits = 0; + +static option long_options[] = { //{"write-raw", required_argument, 0, 'w' }, //{"output", required_argument, 0, 'o' }, {(char* const) "dump-boxes", no_argument, 0, 'd'}, + {(char* const) "disable-limits", no_argument, &option_disable_limits, 1}, {(char* const) "help", no_argument, 0, 'h'}, + {(char* const) "version", no_argument, 0, 'v'}, {0, 0, 0, 0} }; -const char* fourcc_to_string(uint32_t fourcc) -{ - static char fcc[5]; - fcc[0] = (char) ((fourcc >> 24) & 0xFF); - fcc[1] = (char) ((fourcc >> 16) & 0xFF); - fcc[2] = (char) ((fourcc >> 8) & 0xFF); - fcc[3] = (char) ((fourcc >> 0) & 0xFF); - fcc[4] = 0; - return fcc; -} void show_help(const char* argv0) { - fprintf(stderr, " heif-info libheif version: %s\n", heif_get_version()); - fprintf(stderr, "------------------------------------\n"); - fprintf(stderr, "usage: heif-info [options] image.heic\n"); - fprintf(stderr, "\n"); - fprintf(stderr, "options:\n"); - //fprintf(stderr," -w, --write-raw ID write raw compressed data of image 'ID'\n"); - //fprintf(stderr," -o, --output NAME output file name for image selected by -w\n"); - fprintf(stderr, " -d, --dump-boxes show a low-level dump of all MP4 file boxes\n"); - fprintf(stderr, " -h, --help show help\n"); + std::filesystem::path p(argv0); + std::string filename = p.filename().string(); + + std::stringstream sstr; + sstr << " " << filename << " libheif version: " << heif_get_version(); + + std::string title = sstr.str(); + + std::cerr << title << "\n" + << std::string(title.length() + 1, '-') << "\n" + << "Usage: " << filename << " [options] \n" + << "\n" + "options:\n" + //fprintf(stderr," -w, --write-raw ID write raw compressed data of image 'ID'\n"); + //fprintf(stderr," -o, --output NAME output file name for image selected by -w\n"); + " -d, --dump-boxes show a low-level dump of all MP4 file boxes\n" + " --disable-limits disable all security limits (do not use in production environment)\n" + " -h, --help show help\n" + " -v, --version show version\n"; } + +class LibHeifInitializer +{ +public: + LibHeifInitializer() { heif_init(nullptr); } + + ~LibHeifInitializer() { heif_deinit(); } +}; + + int main(int argc, char** argv) { + // This takes care of initializing libheif and also deinitializing it at the end to free all resources. + LibHeifInitializer initializer; + bool dump_boxes = false; bool write_raw_image = false; @@ -105,7 +133,7 @@ int main(int argc, char** argv) while (true) { int option_index = 0; - int c = getopt_long(argc, argv, "dh", long_options, &option_index); + int c = getopt_long(argc, argv, "dhv", long_options, &option_index); if (c == -1) break; @@ -123,6 +151,9 @@ int main(int argc, char** argv) case 'o': output_filename = optarg; break; + case 'v': + heif_examples::show_version(); + return 0; } } @@ -159,33 +190,37 @@ int main(int argc, char** argv) fclose(fh); char fourcc[5]; - fourcc[4]=0; - heif_brand_to_fourcc( heif_read_main_brand(buf,bufSize), fourcc ); + fourcc[4] = 0; + heif_brand_to_fourcc(heif_read_main_brand(buf, bufSize), fourcc); std::cout << "main brand: " << fourcc << "\n"; - heif_brand2* brands=nullptr; - int nBrands=0; - struct heif_error err=heif_list_compatible_brands(buf, n, &brands, &nBrands); + heif_brand2* brands = nullptr; + int nBrands = 0; + heif_error err = heif_list_compatible_brands(buf, n, &brands, &nBrands); if (err.code) { - std::cerr << "error reading brands: " << err.message << "\n"; + std::cerr << "error reading brands: " << err.message << "\n"; } else { - std::cout << "compatible brands: "; - for (int i=0;i0) { - std::cout << ", "; - } - std::cout << fourcc; - } - - std::cout << "\n"; - - heif_free_list_of_compatible_brands(brands); + std::cout << "compatible brands: "; + for (int i = 0; i < nBrands; i++) { + heif_brand_to_fourcc(brands[i], fourcc); + if (i > 0) { + std::cout << ", "; + } + std::cout << fourcc; + } + + std::cout << "\n"; + + heif_free_list_of_compatible_brands(brands); + } + } + else { + if (errno == ENOENT) { + std::cerr << "Input file does not exist.\n"; + exit(10); } } - - std::cout << "\n"; } // ============================================================================== @@ -197,11 +232,21 @@ int main(int argc, char** argv) return 1; } - struct heif_error err; + if (option_disable_limits) { + heif_context_set_security_limits(ctx.get(), heif_get_disabled_security_limits()); + } + + heif_error err; err = heif_context_read_from_file(ctx.get(), input_filename, nullptr); if (dump_boxes) { heif_context_debug_dump_boxes_to_file(ctx.get(), STDOUT_FILENO); // dump to stdout + + if (err.code != 0) { + std::cerr << "Could not read HEIF/AVIF file: " << err.message << "\n"; + return 1; + } + return 0; } @@ -219,8 +264,10 @@ int main(int argc, char** argv) heif_context_get_list_of_top_level_image_IDs(ctx.get(), IDs.data(), numImages); for (int i = 0; i < numImages; i++) { - struct heif_image_handle* handle; - struct heif_error err = heif_context_get_image_handle(ctx.get(), IDs[i], &handle); + std::cout << "\n"; + + heif_image_handle* handle; + heif_error err = heif_context_get_image_handle(ctx.get(), IDs[i], &handle); if (err.code) { std::cerr << err.message << "\n"; return 10; @@ -233,6 +280,75 @@ int main(int argc, char** argv) printf("image: %dx%d (id=%d)%s\n", width, height, IDs[i], primary ? ", primary" : ""); + heif_image_tiling tiling; + err = heif_image_handle_get_image_tiling(handle, true, &tiling); + if (err.code) { + std::cerr << "Error while trying to get image tiling information: " << err.message << "\n"; + } + else if (tiling.num_columns != 1 || tiling.num_rows != 1) { + std::cout << " tiles: " << tiling.num_columns << "x" << tiling.num_rows + << ", tile size: " << tiling.tile_width << "x" << tiling.tile_height << "\n"; + } + + heif_colorspace colorspace; + heif_chroma chroma; + err = heif_image_handle_get_preferred_decoding_colorspace(handle, &colorspace, &chroma); + if (err.code) { + std::cerr << err.message << "\n"; + return 10; + } + + printf(" colorspace: "); + switch (colorspace) { + case heif_colorspace_YCbCr: + printf("YCbCr, "); + break; + case heif_colorspace_RGB: + printf("RGB"); + break; + case heif_colorspace_monochrome: + printf("monochrome"); + break; + case heif_colorspace_nonvisual: + printf("non-visual"); + break; + default: + printf("unknown"); + break; + } + + if (colorspace==heif_colorspace_YCbCr) { + switch (chroma) { + case heif_chroma_420: + printf("4:2:0"); + break; + case heif_chroma_422: + printf("4:2:2"); + break; + case heif_chroma_444: + printf("4:4:4"); + break; + default: + printf("unknown"); + break; + } + } + + printf("\n"); + + // --- bit depth + + int luma_depth = heif_image_handle_get_luma_bits_per_pixel(handle); + int chroma_depth = heif_image_handle_get_chroma_bits_per_pixel(handle); + + printf(" bit depth: "); + if (chroma == heif_chroma_monochrome || luma_depth==chroma_depth) { + printf("%d\n", luma_depth); + } + else { + printf("%d,%d\n", luma_depth, chroma_depth); + } + // --- thumbnails @@ -261,7 +377,7 @@ int main(int argc, char** argv) // --- color profile uint32_t profileType = heif_image_handle_get_color_profile_type(handle); - printf(" color profile: %s\n", profileType ? fourcc_to_string(profileType) : "no"); + printf(" color profile: %s\n", profileType ? heif_examples::fourcc_to_string(profileType).c_str() : "no"); // --- depth information @@ -285,18 +401,22 @@ int main(int argc, char** argv) (void) nDepthImages; if (has_depth) { - struct heif_image_handle* depth_handle; + heif_image_handle* depth_handle; err = heif_image_handle_get_depth_image_handle(handle, depth_id, &depth_handle); if (err.code) { fprintf(stderr, "cannot get depth image: %s\n", err.message); return 1; } - printf(" (%dx%d)\n", + printf(" size: %dx%d\n", heif_image_handle_get_width(depth_handle), heif_image_handle_get_height(depth_handle)); - const struct heif_depth_representation_info* depth_info; + int depth_luma_bpp = heif_image_handle_get_luma_bits_per_pixel(depth_handle); + printf(" bits per pixel: %d\n", depth_luma_bpp); + + + const heif_depth_representation_info* depth_info; if (heif_image_handle_get_depth_image_representation_info(handle, depth_id, &depth_info)) { printf(" z-near: "); @@ -336,20 +456,449 @@ int main(int argc, char** argv) heif_image_handle_release(depth_handle); } + // --- metadata + + int numMetadata = heif_image_handle_get_number_of_metadata_blocks(handle, nullptr); + printf("metadata:\n"); + if (numMetadata > 0) { + std::vector ids(numMetadata); + heif_image_handle_get_list_of_metadata_block_IDs(handle, nullptr, ids.data(), numMetadata); + + for (int n = 0; n < numMetadata; n++) { + std::string itemtype = heif_image_handle_get_metadata_type(handle, ids[n]); + std::string contenttype = heif_image_handle_get_metadata_content_type(handle, ids[n]); + std::string item_uri_type = heif_image_handle_get_metadata_item_uri_type(handle, ids[n]); + std::string ID{"unknown"}; + if (itemtype == "Exif") { + ID = itemtype; + } + else if (itemtype == "uri ") { + ID = itemtype + "/" + item_uri_type; + } + else if (contenttype == "application/rdf+xml") { + ID = "XMP"; + } + else { + ID = itemtype + "/" + contenttype; + } + + printf(" %s: %zu bytes\n", ID.c_str(), heif_image_handle_get_metadata_size(handle, ids[n])); + } + } + else { + printf(" none\n"); + } + + // --- transforms + +#define MAX_PROPERTIES 50 + heif_property_id transforms[MAX_PROPERTIES]; + int nTransforms = heif_item_get_transformation_properties(ctx.get(), + IDs[i], + transforms, + MAX_PROPERTIES); + printf("transformations:\n"); + int image_width = heif_image_handle_get_ispe_width(handle); + int image_height = heif_image_handle_get_ispe_height(handle); + + if (nTransforms) { + for (int k = 0; k < nTransforms; k++) { + switch (heif_item_get_property_type(ctx.get(), IDs[i], transforms[k])) { + case heif_item_property_type_transform_mirror: + printf(" mirror: %s\n", heif_item_get_property_transform_mirror(ctx.get(), IDs[i], transforms[k]) == heif_transform_mirror_direction_horizontal ? "horizontal" : "vertical"); + break; + case heif_item_property_type_transform_rotation: { + int angle = heif_item_get_property_transform_rotation_ccw(ctx.get(), IDs[i], transforms[k]); + printf(" angle (ccw): %d\n", angle); + if (angle==90 || angle==270) { + std::swap(image_width, image_height); + } + break; + } + case heif_item_property_type_transform_crop: { + int left,top,right,bottom; + heif_item_get_property_transform_crop_borders(ctx.get(), IDs[i], transforms[k], image_width, image_height, + &left, &top, &right, &bottom); + printf(" crop: left=%d top=%d right=%d bottom=%d\n", left,top,right,bottom); + break; + } + default: + assert(false); + } + } + } + else { + printf(" none\n"); + } + + // --- regions + + int numRegionItems = heif_image_handle_get_number_of_region_items(handle); + printf("region annotations:\n"); + if (numRegionItems > 0) { + std::vector region_items(numRegionItems); + heif_image_handle_get_list_of_region_item_ids(handle, region_items.data(), numRegionItems); + for (heif_item_id region_item_id : region_items) { + heif_region_item* region_item; + err = heif_context_get_region_item(ctx.get(), region_item_id, ®ion_item); + + uint32_t reference_width, reference_height; + heif_region_item_get_reference_size(region_item, &reference_width, &reference_height); + int numRegions = heif_region_item_get_number_of_regions(region_item); + printf(" id: %u, reference_width: %u, reference_height: %u, %d regions\n", + region_item_id, + reference_width, + reference_height, + numRegions); + + std::vector regions(numRegions); + numRegions = heif_region_item_get_list_of_regions(region_item, regions.data(), numRegions); + for (int j = 0; j < numRegions; j++) { + printf(" region %d\n", j); + heif_region_type type = heif_region_get_type(regions[j]); + if (type == heif_region_type_point) { + int32_t x; + int32_t y; + heif_region_get_point(regions[j], &x, &y); + printf(" point [x=%i, y=%i]\n", x, y); + } + else if (type == heif_region_type_rectangle) { + int32_t x; + int32_t y; + uint32_t w; + uint32_t h; + heif_region_get_rectangle(regions[j], &x, &y, &w, &h); + printf(" rectangle [x=%i, y=%i, w=%u, h=%u]\n", x, y, w, h); + } + else if (type == heif_region_type_ellipse) { + int32_t x; + int32_t y; + uint32_t rx; + uint32_t ry; + heif_region_get_ellipse(regions[j], &x, &y, &rx, &ry); + printf(" ellipse [x=%i, y=%i, r_x=%u, r_y=%u]\n", x, y, rx, ry); + } + else if (type == heif_region_type_polygon) { + int32_t numPoints = heif_region_get_polygon_num_points(regions[j]); + std::vector pts(numPoints*2); + heif_region_get_polygon_points(regions[j], pts.data()); + printf(" polygon ["); + for (int p=0;p pts(numPoints*2); + heif_region_get_polyline_points(regions[j], pts.data()); + printf(" polyline ["); + for (int p=0;p mask_data(data_len); + heif_region_get_inline_mask_data(regions[j], &x, &y, &w, &h, mask_data.data()); + printf(" inline mask [x=%i, y=%i, w=%u, h=%u, data len=%zu]\n", x, y, w, h, mask_data.size()); + } + } + + heif_region_release_many(regions.data(), numRegions); + heif_region_item_release(region_item); + + heif_property_id properties[MAX_PROPERTIES]; + int nDescr = heif_item_get_properties_of_type(ctx.get(), + region_item_id, + heif_item_property_type_user_description, + properties, + MAX_PROPERTIES); + + for (int k = 0; k < nDescr; k++) { + heif_property_user_description* udes; + err = heif_item_get_property_user_description(ctx.get(), + region_item_id, + properties[k], + &udes); + if (err.code == 0) { + printf(" user description:\n"); + printf(" lang: %s\n", udes->lang); + printf(" name: %s\n", udes->name); + printf(" description: %s\n", udes->description); + printf(" tags: %s\n", udes->tags); + heif_property_user_description_release(udes); + } + } + } + } + else { + printf(" none\n"); + } + + // --- text items + int numTextItems = heif_image_handle_get_number_of_text_items(handle); + printf("text items:\n"); + + if (numTextItems > 0) { + std::vector text_items(numTextItems); + heif_image_handle_get_list_of_text_item_ids(handle, text_items.data(), numTextItems); + for (heif_item_id text_item_id : text_items) { + struct heif_text_item* text_item; + err = heif_context_get_text_item(ctx.get(), text_item_id, &text_item); + const char* text_content = heif_text_item_get_content(text_item); + printf(" text item: %s\n", text_content); + heif_string_release(text_content); + + char* elng; + err = heif_item_get_property_extended_language(ctx.get(), + text_item_id, + &elng); + if (err.code == 0) { + printf(" extended language: %s\n", elng); + } + heif_string_release(elng); + } + } + else { + printf(" none\n"); + } + + // --- properties + + printf("properties:\n"); + + // user descriptions + + bool properties_shown = false; + + heif_property_id propertyIds[MAX_PROPERTIES]; + int count; + count = heif_item_get_properties_of_type(ctx.get(), IDs[i], heif_item_property_type_user_description, + propertyIds, MAX_PROPERTIES); + + if (count > 0) { + for (int p = 0; p < count; p++) { + heif_property_user_description* udes; + err = heif_item_get_property_user_description(ctx.get(), IDs[i], propertyIds[p], &udes); + if (err.code) { + std::cerr << "Error reading udes " << IDs[i] << "/" << propertyIds[p] << "\n"; + } + else { + printf(" user description:\n"); + printf(" lang: %s\n", udes->lang); + printf(" name: %s\n", udes->name); + printf(" description: %s\n", udes->description); + printf(" tags: %s\n", udes->tags); + properties_shown = true; + + heif_property_user_description_release(udes); + } + } + } + + // --- camera intrinsic and extrinsic parameters + + if (heif_image_handle_has_camera_intrinsic_matrix(handle)) { + heif_camera_intrinsic_matrix matrix{}; + heif_image_handle_get_camera_intrinsic_matrix(handle, &matrix); + printf(" camera intrinsic matrix:\n"); + printf(" focal length: %f; %f\n", matrix.focal_length_x, matrix.focal_length_y); + printf(" principal point: %f; %f\n", matrix.principal_point_x, matrix.principal_point_y); + printf(" skew: %f\n", matrix.skew); + properties_shown = true; + } + + if (heif_image_handle_has_camera_extrinsic_matrix(handle)) { + heif_camera_extrinsic_matrix* matrix; + heif_image_handle_get_camera_extrinsic_matrix(handle, &matrix); + double rot[9]; + heif_camera_extrinsic_matrix_get_rotation_matrix(matrix, rot); + printf(" camera extrinsic matrix:\n"); + printf(" rotation matrix:\n"); + printf(" %6.3f %6.3f %6.3f\n", rot[0], rot[1], rot[2]); + printf(" %6.3f %6.3f %6.3f\n", rot[3], rot[4], rot[5]); + printf(" %6.3f %6.3f %6.3f\n", rot[6], rot[7], rot[8]); + heif_camera_extrinsic_matrix_release(matrix); + properties_shown = true; + } + + + uint32_t aspect_h, aspect_v; + int has_pasp = heif_image_handle_get_pixel_aspect_ratio(handle, &aspect_h, &aspect_v); + if (has_pasp) { + std::cout << " pixel aspect ratio: " << aspect_h << "/" << aspect_v << "\n"; + properties_shown = true; + } + + heif_content_light_level clli{}; + if (heif_image_handle_get_content_light_level(handle, &clli)) { + std::cout << " content light level (clli):\n" + << " max content light level: " << clli.max_content_light_level << "\n" + << " max pic average light level: " << clli.max_pic_average_light_level << "\n"; + properties_shown = true; + } + + heif_mastering_display_colour_volume mdcv; + if (heif_image_handle_get_mastering_display_colour_volume(handle, &mdcv)) { + + heif_decoded_mastering_display_colour_volume decoded_mdcv; + err = heif_mastering_display_colour_volume_decode(&mdcv, &decoded_mdcv); + + std::cout << " mastering display color volume:\n" + << " display_primaries (x,y): " + << "(" << decoded_mdcv.display_primaries_x[0] << ";" << decoded_mdcv.display_primaries_y[0] << "), " + << "(" << decoded_mdcv.display_primaries_x[1] << ";" << decoded_mdcv.display_primaries_y[1] << "), " + << "(" << decoded_mdcv.display_primaries_x[2] << ";" << decoded_mdcv.display_primaries_y[2] << ")\n"; + + std::cout << " white point (x,y): (" << decoded_mdcv.white_point_x << ";" << decoded_mdcv.white_point_y << ")\n"; + std::cout << " max display mastering luminance: " << decoded_mdcv.max_display_mastering_luminance << "\n"; + std::cout << " min display mastering luminance: " << decoded_mdcv.min_display_mastering_luminance << "\n"; + properties_shown = true; + } + + // --- GIMI + + const char* id = heif_image_handle_get_gimi_content_id(handle); + if (id) { + std::cout << " GIMI content ID: " << id << "\n"; + heif_string_release(id); + properties_shown = true; + } + + // --- cmpd components + + uint32_t num_cmpd_components = heif_image_handle_get_number_of_cmpd_components(handle); + if (num_cmpd_components > 0) { + int num_content_ids = heif_image_handle_has_gimi_component_content_ids(handle); + + auto get_component_type_name = [](uint16_t type) -> const char* { + switch (type) { + case 0: return "monochrome"; + case 1: return "Y"; + case 2: return "Cb"; + case 3: return "Cr"; + case 4: return "red"; + case 5: return "green"; + case 6: return "blue"; + case 7: return "alpha"; + case 8: return "depth"; + case 9: return "disparity"; + case 10: return "palette"; + case 11: return "filter_array"; + case 12: return "padded"; + case 13: return "cyan"; + case 14: return "magenta"; + case 15: return "yellow"; + case 16: return "key_black"; + default: return nullptr; + } + }; + + // Find the maximum display width of the "type_name (N)" column. + size_t max_type_width = 0; + for (uint32_t i = 0; i < num_cmpd_components; i++) { + uint16_t type = heif_image_handle_get_cmpd_component_type(handle, i); + const char* name = get_component_type_name(type); + std::ostringstream tmp; + if (name) { + tmp << name << " (" << type << ")"; + } + else { + tmp << type; + } + max_type_width = std::max(max_type_width, tmp.str().size()); + } + + std::cout << " components:\n"; + for (uint32_t i = 0; i < num_cmpd_components; i++) { + uint16_t type = heif_image_handle_get_cmpd_component_type(handle, i); + const char* type_name = get_component_type_name(type); + + std::ostringstream type_str; + if (type_name) { + type_str << type_name << " (" << type << ")"; + } + else { + type_str << type; + } + + std::cout << " [" << i << "] type: " + << std::left << std::setw(static_cast(max_type_width)) << type_str.str(); + + const char* uri = heif_image_handle_get_cmpd_component_type_uri(handle, i); + if (uri) { + std::cout << " (uri: " << uri << ")"; + heif_string_release(uri); + } + + if (num_content_ids > 0) { + const char* content_id = heif_image_handle_get_gimi_component_content_id(handle, i); + if (content_id) { + std::cout << " content ID: " << content_id; + heif_string_release(content_id); + } + } + + std::cout << "\n"; + } + properties_shown = true; + } + +#if HEIF_WITH_OMAF + // --- OMAF + + if (heif_image_handle_has_omaf_image_projection(handle)) { + heif_omaf_image_projection projection = heif_image_handle_get_omaf_image_projection(handle); + std::cout << " image projection: "; + switch (projection) + { + case heif_omaf_image_projection_equirectangular: + std::cout << "equirectangular"; + break; + case heif_omaf_image_projection_cube_map: + std::cout << "cube-map"; + default: + std::cout << "(unknown)"; + break; + } + std::cout << "\n"; + properties_shown = true; + } +#endif + + if (!properties_shown) { + std::cout << "none\n"; + } + heif_image_handle_release(handle); } #if 0 std::cout << "num images: " << heif_context_get_number_of_top_level_images(ctx.get()) << "\n"; - struct heif_image_handle* handle; + heif_image_handle* handle; err = heif_context_get_primary_image_handle(ctx.get(), &handle); if (err.code != 0) { std::cerr << "Could not get primage image handle: " << err.message << "\n"; return 1; } - struct heif_image* image; + heif_image* image; err = heif_decode_image(handle, &image, heif_colorspace_undefined, heif_chroma_undefined, NULL); if (err.code != 0) { heif_image_handle_release(handle); @@ -361,5 +910,152 @@ int main(int argc, char** argv) heif_image_handle_release(handle); #endif + // ============================================================================== + + heif_context* context = ctx.get(); + + uint32_t nTracks = heif_context_number_of_sequence_tracks(context); + + if (nTracks > 0) { + std::cout << "\n"; + + uint64_t timescale = heif_context_get_sequence_timescale(context); + std::cout << "sequence time scale: " << timescale << " Hz\n"; + + uint64_t duration = heif_context_get_sequence_duration(context); + std::cout << "sequence duration: " << ((double)duration)/(double)timescale << " seconds\n"; + + // TrackOptions + + std::vector track_ids(nTracks); + + heif_context_get_track_ids(context, track_ids.data()); + + for (uint32_t id : track_ids) { + heif_track* track = heif_context_get_track(context, id); + + heif_track_type handler = heif_track_get_track_handler_type(track); + std::cout << "track " << id << "\n"; + std::cout << " handler: '" << heif_examples::fourcc_to_string(handler) << "' = "; + + switch (handler) { + case heif_track_type_image_sequence: + std::cout << "image sequence\n"; + break; + case heif_track_type_video: + std::cout << "video\n"; + break; + case heif_track_type_auxiliary: + std::cout << "auxiliary "; + { + heif_auxiliary_track_info_type type = heif_track_get_auxiliary_info_type(track); + switch (type) { + case heif_auxiliary_track_info_type_unknown: { + const char* s = heif_track_get_auxiliary_info_type_urn(track); + std::cout << "(unknown: " << s << ")\n"; + heif_string_release(s); + break; + } + case heif_auxiliary_track_info_type_alpha: + std::cout << "(alpha)\n"; + break; + } + } + break; + case heif_track_type_metadata: + std::cout << "metadata\n"; + break; + default: + std::cout << "unknown\n"; + break; + } + + if (handler == heif_track_type_video || + handler == heif_track_type_image_sequence) { + uint16_t w, h; + heif_track_get_image_resolution(track, &w, &h); + std::cout << " resolution: " << w << "x" << h << "\n"; + } + + uint32_t sampleEntryType = heif_track_get_sample_entry_type_of_first_cluster(track); + std::cout << " sample entry type: " << heif_examples::fourcc_to_string(sampleEntryType) << "\n"; + + if (sampleEntryType == heif_fourcc('u', 'r', 'i', 'm')) { + const char* uri; + err = heif_track_get_urim_sample_entry_uri_of_first_cluster(track, &uri); + if (err.code) { + std::cerr << "error reading urim-track uri: " << err.message << "\n"; + } + std::cout << " uri: " << uri << "\n"; + heif_string_release(uri); + } + + std::cout << " sample auxiliary information: "; + int nSampleAuxTypes = heif_track_get_number_of_sample_aux_infos(track); + + std::vector aux_types(nSampleAuxTypes); + heif_track_get_sample_aux_info_types(track, aux_types.data()); + + for (size_t i=0;i 0) { + std::cout << " references:\n"; + std::vector refTypes(nRefTypes); + heif_track_get_track_reference_types(track, refTypes.data()); + + for (uint32_t refType : refTypes) { + std::cout << " " << heif_examples::fourcc_to_string(refType) << ": "; + + size_t n = heif_track_get_number_of_track_reference_of_type(track, refType); + std::vector track_ids(n); + heif_track_get_references_from_track(track, refType, track_ids.data()); + for (size_t i=0;i0) std::cout << ", "; + std::cout << "track#" << track_ids[i]; + } + std::cout << "\n"; + } + } + + const char* gimi_track_id = heif_track_get_gimi_track_content_id(track); + if (gimi_track_id) { + std::cout << " GIMI track id: " << gimi_track_id << "\n"; + heif_string_release(gimi_track_id); + } + + heif_track_release(track); + } + } + + // ============================================================================== + + { + int nItems = heif_context_get_number_of_items(context); + if (nItems > 0) { + std::cout << "MIME items:\n"; + std::vector item_ids(nItems); + heif_context_get_list_of_item_IDs(context, item_ids.data(), nItems); + + for (auto id : item_ids) { + uint32_t type = heif_item_get_item_type(context, id); + if (type == heif_item_type_mime) { + const char* mimeType = heif_item_get_mime_item_content_type(context, id); + std::cout << " " << id << " : " << mimeType << "\n"; + } + } + } + } + return 0; } diff --git a/examples/heif_test.cc b/examples/heif_test.cc index e1d38269c8..e447255883 100644 --- a/examples/heif_test.cc +++ b/examples/heif_test.cc @@ -3,7 +3,7 @@ MIT License - Copyright (c) 2017 struktur AG, Dirk Farin + Copyright (c) 2017 Dirk Farin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -23,9 +23,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#if defined(HAVE_CONFIG_H) -#include "config.h" -#endif #include #include @@ -45,6 +42,7 @@ #include #include #include +#include "common.h" static struct option long_options[] = { @@ -52,6 +50,7 @@ static struct option long_options[] = { //{"output", required_argument, 0, 'o' }, {(char* const) "decode-img", required_argument, 0, 'd'}, {(char* const) "metadata", required_argument, 0, 'm'}, + {(char* const) "version", required_argument, 0, 'v'}, {0, 0, 0, 0} }; @@ -65,10 +64,11 @@ void show_help(const char* argv0) fprintf(stderr, " -d, --decode-img ID decode image and output raw pixel data of all planes\n"); fprintf(stderr, " -m, --metadata ID output metadata\n"); fprintf(stderr, " -h, --help show help\n"); + fprintf(stderr, " -v, --version show version\n"); } -std::pair parse_id_pair(std::string s) +std::pair parse_id_pair(const std::string& s) { std::string::size_type p = s.find_first_of(':'); if (p == std::string::npos) { @@ -90,7 +90,7 @@ int main(int argc, char** argv) while (true) { int option_index = 0; - int c = getopt_long(argc, argv, "d:m:h", long_options, &option_index); + int c = getopt_long(argc, argv, "d:m:hv", long_options, &option_index); if (c == -1) break; @@ -104,6 +104,9 @@ int main(int argc, char** argv) case 'h': show_help(argv[0]); return 0; + case 'v': + heif_examples::show_version(); + return 0; } } @@ -146,8 +149,8 @@ int main(int argc, char** argv) int height = img.get_height(channel); int bytes = (img.get_bits_per_pixel(channel) + 7) / 8; - int stride; - const uint8_t* p = img.get_plane(channel, &stride); + size_t stride; + const uint8_t* p = img.get_plane2(channel, &stride); for (int y = 0; y < height; y++) { fwrite(p + y * stride, width, bytes, stdout); } diff --git a/examples/heif_thumbnailer.cc b/examples/heif_thumbnailer.cc index 93b2c77baf..a2d13be290 100644 --- a/examples/heif_thumbnailer.cc +++ b/examples/heif_thumbnailer.cc @@ -3,7 +3,7 @@ MIT License - Copyright (c) 2018 struktur AG, Dirk Farin + Copyright (c) 2018 Dirk Farin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -23,10 +23,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#if defined(HAVE_CONFIG_H) -# include "config.h" -#endif - #if defined(HAVE_UNISTD_H) # include @@ -35,11 +31,13 @@ #include #include #include +#include #include -#include "encoder.h" +#include "heifio/encoder.h" #if HAVE_LIBPNG -# include "encoder_png.h" +# include "heifio/encoder_png.h" +#include "common.h" #endif #if defined(_MSC_VER) @@ -55,13 +53,23 @@ static int usage(const char* command) } +class LibHeifInitializer { +public: + LibHeifInitializer() { heif_init(nullptr); } + ~LibHeifInitializer() { heif_deinit(); } +}; + + int main(int argc, char** argv) { + // This takes care of initializing libheif and also deinitializing it at the end to free all resources. + LibHeifInitializer initializer; + int opt; int size = 512; // default thumbnail size bool thumbnail_from_primary_image_only = false; - while ((opt = getopt(argc, argv, "s:hp")) != -1) { + while ((opt = getopt(argc, argv, "s:hpv")) != -1) { switch (opt) { case 's': size = atoi(optarg); @@ -69,6 +77,9 @@ int main(int argc, char** argv) case 'p': thumbnail_from_primary_image_only = true; break; + case 'v': + heif_examples::show_version(); + return 0; case 'h': default: return usage(argv[0]); @@ -158,10 +169,10 @@ int main(int argc, char** argv) int input_width = heif_image_handle_get_width(image_handle); int input_height = heif_image_handle_get_height(image_handle); - int thumbnail_width = input_width; - int thumbnail_height = input_height; - if (input_width > size || input_height > size) { + int thumbnail_width; + int thumbnail_height; + if (input_width > input_height) { thumbnail_height = input_height * size / input_width; thumbnail_width = size; @@ -174,6 +185,11 @@ int main(int argc, char** argv) thumbnail_width = thumbnail_height = 0; } + if (thumbnail_width == 0 || thumbnail_height == 0) { + std::cerr << "Zero thumbnail output size\n"; + return 1; + } + // --- output thumbnail smaller than HEIF thumbnail -> scale down diff --git a/examples/heif_view.cc b/examples/heif_view.cc new file mode 100644 index 0000000000..15c96dccf9 --- /dev/null +++ b/examples/heif_view.cc @@ -0,0 +1,465 @@ +/* + libheif example application. + + MIT License + + Copyright (c) 2025 Dirk Farin + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#include +#include +#include "libheif/heif_experimental.h" +#include "libheif/heif_sequences.h" +#include + +#if defined(HAVE_UNISTD_H) + +#include + +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "common.h" +#include "sdl.hh" + +#if defined(_MSC_VER) +#include "getopt.h" +#endif + +#if !SDL_VERSION_ATLEAST(2, 0, 18) +#define SDL_GetTicks64 SDL_GetTicks +#endif + +#define UNUSED(x) (void)x + + +static void show_help(const char* argv0) +{ + std::cerr << " " << argv0 << " libheif version: " << heif_get_version() << "\n" + << "---------------------------------------\n" + "Usage: " << argv0 << " [options] \n" + << "\n" + "Options:\n" + " -h, --help show help\n" + " -v, --version show version\n" + " --list-decoders list all available decoders (built-in and plugins)\n" + " -d, --decoder ID use a specific decoder (see --list-decoders)\n" + " --speedup FACTOR increase playback speed by FACTOR\n" + " --show-sai show sample auxiliary information\n" + " --show-frame-duration show each frame duration in milliseconds\n" + " --show-track-metadata show metadata attached to the track (e.g. TAI config)\n" + " --show-metadata-text show data in metadata track as text\n" + " --show-metadata-hex show data in metadata track as hex bytes\n" + " --show-all show all extra information\n" + " --ignore-editlist show the raw media timeline without repetitions\n"; +} + + +class ContextReleaser { +public: + ContextReleaser(struct heif_context* ctx) : ctx_(ctx) {} + + ~ContextReleaser() + { + heif_context_free(ctx_); + } + +private: + struct heif_context* ctx_; +}; + + +int option_list_decoders = 0; +double option_speedup = 1.0; +bool option_show_sai = false; +bool option_show_frame_duration = false; +bool option_show_track_metadata = false; +bool option_ignore_editlist = false; +enum { + metadata_output_none, + metadata_output_text, + metadata_output_hex +} option_metadata_output = metadata_output_none; + +const int OPTION_SPEEDUP = 1000; +const int OPTION_SHOW_SAI = 1001; +const int OPTION_SHOW_FRAME_DURATION = 1002; +const int OPTION_SHOW_TRACK_METADATA = 1003; +const int OPTION_SHOW_ALL = 1004; +const int OPTION_SHOW_METADATA_TEXT = 1005; +const int OPTION_SHOW_METADATA_HEX = 1006; +const int OPTION_IGNORE_EDITLIST = 1007; + +static struct option long_options[] = { + {(char* const) "decoder", required_argument, 0, 'd'}, + {(char* const) "list-decoders", no_argument, &option_list_decoders, 1}, + {(char* const) "help", no_argument, 0, 'h'}, + {(char* const) "version", no_argument, 0, 'v'}, + {(char* const) "speedup", required_argument, 0, OPTION_SPEEDUP}, + {(char* const) "show-sai", no_argument, 0, OPTION_SHOW_SAI}, + {(char* const) "show-frame-duration", no_argument, 0, OPTION_SHOW_FRAME_DURATION}, + {(char* const) "show-track-metadata", no_argument, 0, OPTION_SHOW_TRACK_METADATA}, + {(char* const) "show-metadata-text", no_argument, 0, OPTION_SHOW_METADATA_TEXT}, + {(char* const) "show-metadata-hex", no_argument, 0, OPTION_SHOW_METADATA_HEX}, + {(char* const) "show-all", no_argument, 0, OPTION_SHOW_ALL}, + {(char* const) "ignore-editlist", no_argument, 0, OPTION_IGNORE_EDITLIST}, + {nullptr, no_argument, nullptr, 0} +}; + + +void output_hex(const uint8_t* data, size_t size) +{ + std::cout << std::hex << std::setfill('0'); + + for (size_t i=0;itime_uncertainty << " / " << taic->clock_resolution << " / " + << taic->clock_drift_rate << " / " << int(taic->clock_type) << "\n"; + } + } + + + // --- find metadata track + + heif_track* metadata_track = nullptr; + if (option_metadata_output != metadata_output_none) { + uint32_t metadata_track_id; + size_t nMetadataTracks = heif_track_find_referring_tracks(track, heif_track_reference_type_description, &metadata_track_id, 1); + + if (nMetadataTracks == 1) { + metadata_track = heif_context_get_track(ctx, metadata_track_id); + } + } + + + // --- open output window + + SDL_YUV_Display sdlWindow; + std::optional sdlError = sdlWindow.init(w, h, SDL_YUV_Display::SDL_CHROMA_420, "heif-view"); + if (sdlError) { + std::cerr << "Cannot open output window. " << *sdlError << "\n"; + return 10; + } + + std::unique_ptr decode_options(heif_decoding_options_alloc(), heif_decoding_options_free); + decode_options->convert_hdr_to_8bit = true; + decode_options->decoder_id = decoder_id; + decode_options->ignore_sequence_editlist = option_ignore_editlist; + + + // --- decoding loop + + for (int frameNr = 1;; frameNr++) { + heif_image* out_image = nullptr; + + // --- decode next sequence image + + err = heif_track_decode_next_image(track, &out_image, + heif_colorspace_YCbCr, // TODO: find best format + heif_chroma_420, + decode_options.get()); + if (err.code == heif_error_End_of_sequence) { + break; + } + else if (err.code) { + std::cerr << err.message << "\n"; + return 1; + } + + // --- wait for image presentation time + + uint32_t duration = heif_image_get_duration(out_image); + uint64_t timescale = heif_track_get_timescale(track); + uint64_t duration_ms = duration * 1000 / timescale; + + if (option_show_frame_duration) { + std::cout << "sample duration " << heif_image_get_duration(out_image) << " = " << duration_ms << " ms\n"; + } + + static const uint64_t start_time = SDL_GetTicks64(); + static uint64_t next_frame_pts = 0; + + uint64_t now_time = SDL_GetTicks64(); + uint64_t elapsed_time = (now_time - start_time); + if (elapsed_time < next_frame_pts) { + SDL_Delay((Uint32)(next_frame_pts - elapsed_time)); + } + + next_frame_pts += static_cast(static_cast(duration_ms) / option_speedup); + + + // --- display image + + size_t stride_Y, stride_Cb, stride_Cr; + const uint8_t* p_Y = heif_image_get_plane_readonly2(out_image, heif_channel_Y, &stride_Y); + const uint8_t* p_Cb = heif_image_get_plane_readonly2(out_image, heif_channel_Cb, &stride_Cb); + const uint8_t* p_Cr = heif_image_get_plane_readonly2(out_image, heif_channel_Cr, &stride_Cr); + + sdlWindow.display(p_Y, p_Cb, p_Cr, static_cast(stride_Y), static_cast(stride_Cb)); + + if (show_frame_number) { + std::cout << "--- frame " << frameNr << "\n"; + } + + if (option_show_sai) { + const char* contentID = heif_image_get_gimi_sample_content_id(out_image); + if (contentID) { + std::cout << "GIMI content id: " << contentID << "\n"; + heif_string_release(contentID); + } + + heif_tai_timestamp_packet* timestamp; + err = heif_image_get_tai_timestamp(out_image, ×tamp); + if (err.code) { + std::cerr << err.message << "\n"; + return 10; + } + else if (timestamp) { + std::cout << "TAI timestamp: " << timestamp->tai_timestamp << "\n"; + heif_tai_timestamp_packet_release(timestamp); + } + } + + // --- get metadata sample + + static heif_raw_sequence_sample* metadata_sample = nullptr; + static uint64_t metadata_sample_display_time = 0; + + if (metadata_track && metadata_sample == nullptr) { + err = heif_track_get_next_raw_sequence_sample(metadata_track, &metadata_sample); + if (err.code != heif_error_Ok && err.code != heif_error_End_of_sequence) { + std::cerr << err.message << "\n"; + return 10; + } + } + + // --- show metadata + + while (metadata_sample && static_cast((double)metadata_sample_display_time / option_speedup) <= elapsed_time) { + size_t size; + const uint8_t* data = heif_raw_sequence_sample_get_data(metadata_sample, &size); + + std::cout << "timestamp: " << ((double)metadata_sample_display_time)/1000.0f << "sec\n"; + + if (option_metadata_output == metadata_output_text) { + std::cout << ((const char*) data) << "\n"; + } + else if (option_metadata_output == metadata_output_hex) { + output_hex(data, size); + std::cout << '\n'; + } + + uint64_t metadata_timescale = heif_track_get_timescale(metadata_track); + uint32_t metadata_duration = heif_raw_sequence_sample_get_duration(metadata_sample); + metadata_sample_display_time += metadata_duration * 1000 / metadata_timescale; + + heif_raw_sequence_sample_release(metadata_sample); + metadata_sample = nullptr; + + // get next metadata sample + + err = heif_track_get_next_raw_sequence_sample(metadata_track, &metadata_sample); + if (err.code != heif_error_Ok && err.code != heif_error_End_of_sequence) { + std::cerr << err.message << "\n"; + return 10; + } + } + + heif_image_release(out_image); + + if (sdlWindow.doQuit()) { + break; + } + } + + sdlWindow.close(); + + heif_track_release(track); + heif_track_release(metadata_track); + + return 0; +} diff --git a/examples/sdl.cc b/examples/sdl.cc new file mode 100644 index 0000000000..051a8d9f5e --- /dev/null +++ b/examples/sdl.cc @@ -0,0 +1,302 @@ +/* + This file is part of dec265, an example application using libde265. + + MIT License + + Copyright (c) 2013-2014 struktur AG, Dirk Farin + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#include "sdl.hh" +#include + + +std::optional SDL_YUV_Display::init(int frame_width, int frame_height, enum SDL_Chroma chroma, + const char* window_title) +{ + // reduce image size to a multiple of 8 (apparently required by YUV overlay) + + frame_width &= ~7; + frame_height &= ~7; + + mChroma = chroma; + + if (SDL_Init(SDL_INIT_VIDEO) < 0 ) { + std::stringstream sstr; + sstr << "SDL_Init() failed: " << SDL_GetError(); + SDL_Quit(); + return sstr.str(); + } + + // set window title + mWindow = SDL_CreateWindow(window_title, + SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, + frame_width, frame_height, 0); + if (!mWindow) { + std::stringstream sstr; + sstr << "SDL: Couldn't set video mode to " << frame_width << "x" << frame_height << ": " << SDL_GetError(); + SDL_Quit(); + return sstr.str(); + } + + Uint32 flags = 0; // Empty flags prioritize SDL_RENDERER_ACCELERATED. + mRenderer = SDL_CreateRenderer(mWindow, -1, flags); + if (!mRenderer) { + std::stringstream sstr; + sstr << "SDL: Couldn't create renderer: " << SDL_GetError(); + SDL_Quit(); + return sstr.str(); + } + + Uint32 pixelFormat = 0; + switch (mChroma) { + case SDL_CHROMA_MONO: pixelFormat = SDL_PIXELFORMAT_YV12; break; + case SDL_CHROMA_420: pixelFormat = SDL_PIXELFORMAT_YV12; break; + case SDL_CHROMA_422: pixelFormat = SDL_PIXELFORMAT_YV12; break; + case SDL_CHROMA_444: pixelFormat = SDL_PIXELFORMAT_YV12; break; + default: { + std::stringstream sstr; + sstr << "Unsupported chroma: " << (int)mChroma; + SDL_Quit(); + return sstr.str(); + } + } + + mTexture = SDL_CreateTexture(mRenderer, pixelFormat, + SDL_TEXTUREACCESS_STREAMING, frame_width, frame_height); + if (!mTexture ) { + std::stringstream sstr; + sstr << "SDL: Couldn't create SDL texture: " << SDL_GetError(); + SDL_Quit(); + return sstr.str(); + } + + rect.x = 0; + rect.y = 0; + rect.w = frame_width; + rect.h = frame_height; + + mWindowOpen=true; + + return {}; +} + +void SDL_YUV_Display::display(const unsigned char *Y, + const unsigned char *U, + const unsigned char *V, + int stride, int chroma_stride) +{ + if (!mWindowOpen) return; + if (SDL_LockTexture(mTexture, nullptr, + reinterpret_cast(&mPixels), &mStride) < 0) return; + + if (mChroma == SDL_CHROMA_420) { + display420(Y,U,V,stride,chroma_stride); + } + else if (mChroma == SDL_CHROMA_422) { + display422(Y,U,V,stride,chroma_stride); + } + else if (mChroma == SDL_CHROMA_444) { + display444as420(Y,U,V,stride,chroma_stride); + //display444as422(Y,U,V,stride,chroma_stride); + } + else if (mChroma == SDL_CHROMA_MONO) { + display400(Y,stride); + } + + SDL_UnlockTexture(mTexture); + + SDL_RenderCopy(mRenderer, mTexture, nullptr, nullptr); + SDL_RenderPresent(mRenderer); +} + + +void SDL_YUV_Display::display420(const unsigned char *Y, + const unsigned char *U, + const unsigned char *V, + int stride, int chroma_stride) +{ + if (stride == mStride && chroma_stride == mStride/2) { + + // fast copy + + memcpy(mPixels, Y, rect.w * rect.h); + memcpy(&mPixels[rect.w * rect.h], V, rect.w * rect.h / 4); + memcpy(&mPixels[(rect.w * rect.h) + (rect.w * rect.h / 4)], U, rect.w * rect.h / 4); + } + else { + // copy line by line, because sizes are different + uint8_t *dest = mPixels; + + for (int y=0;y((U[(y + 0) * chroma_stride + x] + U[(y + 0) * chroma_stride + x + 1] + + U[(y + 1) * chroma_stride + x] + U[(y + 1) * chroma_stride + x + 1]) / 4); + v[x / 2] = static_cast((V[(y + 0) * chroma_stride + x] + V[(y + 0) * chroma_stride + x + 1] + + V[(y + 1) * chroma_stride + x] + V[(y + 1) * chroma_stride + x + 1]) / 4); + + //u[x/2] = U[y*chroma_stride + x]; + //v[x/2] = V[y*chroma_stride + x]; + } + } +} + + +bool SDL_YUV_Display::doQuit() const +{ + SDL_Event event; + while (SDL_PollEvent(&event)) { + if (event.type == SDL_QUIT) { + return true; + } + } + + return false; +} + +void SDL_YUV_Display::close() +{ + if (mTexture) { + SDL_DestroyTexture(mTexture); + mTexture = nullptr; + } + if (mRenderer) { + SDL_DestroyRenderer(mRenderer); + mRenderer = nullptr; + } + if (mWindow) { + SDL_DestroyWindow(mWindow); + mWindow = nullptr; + } + SDL_Quit(); + + mWindowOpen=false; +} diff --git a/examples/sdl.hh b/examples/sdl.hh new file mode 100644 index 0000000000..6fc43d1b5a --- /dev/null +++ b/examples/sdl.hh @@ -0,0 +1,82 @@ +/* + This file is part of dec265, an example application using libde265. + + MIT License + + Copyright (c) 2013-2014 struktur AG, Dirk Farin + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. + */ + +#include +#include +#include + + +class SDL_YUV_Display +{ +public: + + enum SDL_Chroma { + SDL_CHROMA_MONO=400, + SDL_CHROMA_420 =420, + SDL_CHROMA_422 =422, + SDL_CHROMA_444 =444 + }; + + // Returns error message or nullopt on success. + std::optional init(int frame_width, int frame_height, enum SDL_Chroma chroma, const char* window_title); + void display(const unsigned char *Y, const unsigned char *U, const unsigned char *V, + int stride, int chroma_stride); + void close(); + + bool doQuit() const; + + bool isOpen() const { return mWindowOpen; } + +private: + SDL_Window *mWindow = nullptr; + SDL_Renderer *mRenderer = nullptr; + SDL_Texture *mTexture = nullptr; + SDL_Rect rect; + bool mWindowOpen; + uint8_t *mPixels = nullptr; + int mStride = 0; + + SDL_Chroma mChroma; + + void display400(const unsigned char *Y, + int stride); + void display420(const unsigned char *Y, + const unsigned char *U, + const unsigned char *V, + int stride, int chroma_stride); + void display422(const unsigned char *Y, + const unsigned char *U, + const unsigned char *V, + int stride, int chroma_stride); + void display444as422(const unsigned char *Y, + const unsigned char *U, + const unsigned char *V, + int stride, int chroma_stride); + void display444as420(const unsigned char *Y, + const unsigned char *U, + const unsigned char *V, + int stride, int chroma_stride); +}; diff --git a/examples/test_c_api.c b/examples/test_c_api.c index 3649a7cbf7..bd3a6fc341 100644 --- a/examples/test_c_api.c +++ b/examples/test_c_api.c @@ -4,7 +4,7 @@ MIT License - Copyright (c) 2018 struktur AG, Dirk Farin + Copyright (c) 2018 Dirk Farin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/examples/utf8.manifest b/examples/utf8.manifest new file mode 100644 index 0000000000..dab929e151 --- /dev/null +++ b/examples/utf8.manifest @@ -0,0 +1,8 @@ + + + + + UTF-8 + + + diff --git a/examples/utf8.rc b/examples/utf8.rc new file mode 100644 index 0000000000..62bdbdc362 --- /dev/null +++ b/examples/utf8.rc @@ -0,0 +1,3 @@ +#include + +CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "utf8.manifest" diff --git a/examples/vmt.cc b/examples/vmt.cc new file mode 100644 index 0000000000..c0e715e404 --- /dev/null +++ b/examples/vmt.cc @@ -0,0 +1,314 @@ +/* + libheif example application "heif-enc" - VMT metadata track support. + + MIT License + + Copyright (c) 2025 Dirk Farin + Copyright (c) 2026 Rob Smith + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#include "vmt.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + + +static const uint32_t BAD_VMT_TIMESTAMP = 0xFFFFFFFE; + + +static std::optional nibble_to_val(char c) +{ + if (c>='0' && c<='9') { + return c - '0'; + } + if (c>='a' && c<='f') { + return c - 'a' + 10; + } + if (c>='A' && c<='F') { + return c - 'A' + 10; + } + + return std::nullopt; +} + +// Convert hex data to raw binary. Ignore any non-hex characters. +static std::vector hex_to_binary(const std::string& line) +{ + std::vector data; + uint8_t current_value = 0; + + bool high_nibble = true; + for (auto c : line) { + auto v = nibble_to_val(c); + if (v) { + if (high_nibble) { + current_value = static_cast(*v << 4); + high_nibble = false; + } + else { + current_value |= *v; + data.push_back(current_value); + high_nibble = true; + } + } + } + + return data; +} + + +// Convert base64 data to raw binary. +static std::vector decode_base64(const std::string& line) +{ + const std::string base64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + std::vector data; + + size_t len = line.size(); + if (len % 4 != 0) { + len = int(len / 4) * 4; + } + + for (size_t i = 0; i < len; i += 4) { + uint8_t buf[4]; + for (uint8_t j = 0; j < 4; j++) { + size_t k = base64.find(line[i + j]); + buf[j] = (uint8_t)(k == std::string::npos ? base64.size() : k); + } + + data.push_back((uint8_t)(buf[0] << 2) + (buf[1] >> 4)); + + if (line[i + 2] != '=') { + data.push_back((uint8_t)((buf[1] & 0x0f) << 4) + (buf[2] >> 2)); + } + + if (line[i + 3] != '=') { + data.push_back((uint8_t)((buf[2] & 0x03) << 6) + buf[3]); + } + } + + return data; +} + + +// Parse metadata from WebVMT sync commmands +static std::vector parse_vmt_sync_data(const std::string& content) +{ + std::vector data; + + std::regex pattern_sync(R"(\s*\{\s*\"sync\"\s*:\s*\{(.*?)\}\s*\}\s*)"); + const std::sregex_token_iterator ti_end; + + for (std::sregex_token_iterator ti(content.begin(), content.end(), pattern_sync, 1); ti != ti_end; ++ti) + { + std::string sync = *ti; + std::regex pattern_type(R"(.*\"type\"\s*:\s*\"(.*?)\".*)"); + std::regex pattern_data(R"(.*\"data\"\s*:\s*\"(.*?)\".*)"); + std::smatch match; + + if (std::regex_match(sync, match, pattern_type)) { + std::string type = match[1]; + + std::regex pattern_hex(R"(.*\.hex$)"); + std::regex pattern_b64(R"(.*\.base64$)"); + + std::string textData; + if (std::regex_match(sync, match, pattern_data)) { + textData = match[1]; + } + + if (std::regex_match(type, match, pattern_hex)) { + std::vector binaryData = hex_to_binary(textData); + data.insert(data.end(), binaryData.begin(), binaryData.end()); + } + else if (std::regex_match(type, match, pattern_b64)) { + std::vector binaryData = decode_base64(textData); + data.insert(data.end(), binaryData.begin(), binaryData.end()); + } + else { + data.insert(data.end(), textData.data(), textData.data() + textData.length()); + } + } + } + + return data; +} + + +static uint32_t parse_vmt_timestamp(const std::string& vmt_time) +{ + std::regex pattern(R"(-?((\d*):)?(\d\d):(\d\d)(\.(\d*))?)"); + std::smatch match; + + if (!std::regex_match(vmt_time, match, pattern)) { + return 0; // no match + } + + std::string hh = match[2]; // optional + std::string mm = match[3]; + std::string ss = match[4]; + std::string fs = match[6]; // optional + + if (vmt_time.find('-') != std::string::npos) { + return 0; // negative time not supported + } + + uint32_t ms = 0; + + if (fs != "") { + if (fs.length() == 3) { + ms = std::stoi(fs); + } + else { + return BAD_VMT_TIMESTAMP; // invalid + } + } + + uint32_t ts = ((hh != "" ? std::stoi(hh) : 0) * 3600 * 1000 + + std::stoi(mm) * 60 * 1000 + + std::stoi(ss) * 1000 + + ms); + + return ts; +} + +int encode_vmt_metadata_track(heif_context* context, heif_track* visual_track, + const std::string& vmt_metadata_file, + const std::string& track_uri, bool binary) +{ + // --- add metadata track + + heif_track* track = nullptr; + + heif_track_options* track_options = heif_track_options_alloc(); + heif_track_options_set_timescale(track_options, 1000); + + heif_context_add_uri_metadata_sequence_track(context, track_uri.c_str(), track_options, &track); + heif_raw_sequence_sample* sample = heif_raw_sequence_sample_alloc(); + + + std::ifstream istr(vmt_metadata_file.c_str()); + + std::regex pattern_cue(R"(^\s*(-?(\d|:|\.)*)\s*-->\s*(-?(\d|:|\.)*)?.*)"); + std::regex pattern_note(R"(^\s*(NOTE).*)"); + + static std::vector prev_metadata; + static std::optional prev_ts; + + std::string line; + while (std::getline(istr, line)) + { + std::smatch match; + + if (std::regex_match(line, match, pattern_note)) { + while (std::getline(istr, line)) { + if (line.empty()) { + break; + } + } + + continue; + } + + if (!std::regex_match(line, match, pattern_cue)) { + continue; + } + + std::string cue_start = match[1]; + std::string cue_end = match[3]; // == "" for unbounded cues + + uint32_t ts = parse_vmt_timestamp(cue_start); + + std::vector concat; + + if (binary) { + while (std::getline(istr, line)) { + if (line.empty()) { + break; + } + + std::vector binaryData = hex_to_binary(line); + concat.insert(concat.end(), binaryData.begin(), binaryData.end()); + } + + } + else { + while (std::getline(istr, line)) { + if (line.empty()) { + break; + } + + concat.insert(concat.end(), line.data(), line.data() + line.length()); + concat.push_back('\n'); + } + + concat.push_back(0); + std::string content(concat.begin(), concat.end()); + concat = parse_vmt_sync_data(content); + } + + if (ts != BAD_VMT_TIMESTAMP) { + + if (ts > *prev_ts) { + heif_raw_sequence_sample_set_data(sample, (const uint8_t*)prev_metadata.data(), prev_metadata.size()); + heif_raw_sequence_sample_set_duration(sample, ts - *prev_ts); + heif_track_add_raw_sequence_sample(track, sample); + } + else if (ts == *prev_ts) { + concat.insert(concat.begin(), prev_metadata.begin(), prev_metadata.end()); + } + else { + std::cerr << "Bad WebVMT timestamp order: " << cue_start << "\n"; + } + + prev_ts = ts; + prev_metadata = concat; + } + else { + std::cerr << "Bad WebVMT timestamp: " << cue_start << "\n"; + } + } + + // --- flush last metadata packet + + heif_raw_sequence_sample_set_data(sample, (const uint8_t*)prev_metadata.data(), prev_metadata.size()); + heif_raw_sequence_sample_set_duration(sample, 1); + heif_track_add_raw_sequence_sample(track, sample); + + // --- add track reference + + heif_track_add_reference_to_track(track, heif_track_reference_type_description, visual_track); + + // --- release all objects + + heif_raw_sequence_sample_release(sample); + heif_track_options_release(track_options); + heif_track_release(track); + + return 0; +} diff --git a/examples/vmt.h b/examples/vmt.h new file mode 100644 index 0000000000..3c65303906 --- /dev/null +++ b/examples/vmt.h @@ -0,0 +1,38 @@ +/* + libheif example application "heif-enc" - VMT metadata track support. + + MIT License + + Copyright (c) 2025 Dirk Farin + Copyright (c) 2026 Rob Smith + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#pragma once + +#include +#include + +struct heif_context; +struct heif_track; + +int encode_vmt_metadata_track(heif_context* context, heif_track* visual_track, + const std::string& vmt_metadata_file, + const std::string& track_uri, bool binary); diff --git a/extra/Makefile.am b/extra/Makefile.am deleted file mode 100644 index 465d9a5636..0000000000 --- a/extra/Makefile.am +++ /dev/null @@ -1,4 +0,0 @@ -EXTRA_DIST = \ - getopt.c \ - getopt.h \ - getopt_long.c diff --git a/extra/getopt.h b/extra/getopt.h index b23a4fbeee..f5d3bfaad1 100644 --- a/extra/getopt.h +++ b/extra/getopt.h @@ -55,8 +55,8 @@ struct option #define required_argument 1 #define optional_argument 2 -int getopt(int, char**, char*); -int getopt_long(int, char**, char*, struct option*, int*); +int getopt(int, char**, const char*); +int getopt_long(int, char**, const char*, struct option*, int*); #ifdef __cplusplus } diff --git a/extra/getopt_long.c b/extra/getopt_long.c index 8dfda80049..a1d5055260 100644 --- a/extra/getopt_long.c +++ b/extra/getopt_long.c @@ -73,7 +73,7 @@ __progname(char * nargv0) * Parse argc/argv argument vector. */ int -getopt_internal(int nargc, char ** nargv, const char *ostr) +getopt_internal(int nargc, char * const* nargv, const char *ostr) { static char *place = EMSG; /* option letter processing */ const char *oli; /* option letter list index */ @@ -142,7 +142,7 @@ getopt2(int nargc, char * nargv, const char *ostr) if ((retval = getopt_internal(nargc, nargv, ostr)) == -2) { retval = -1; - ++optind; + ++optind; } return(retval); } @@ -153,7 +153,7 @@ getopt2(int nargc, char * nargv, const char *ostr) * Parse argc/argv argument vector. */ int -getopt_long(int nargc, char ** nargv, char * options, struct option * long_options, int * index) +getopt_long(int nargc, char ** nargv, const char * options, struct option * long_options, int * index) { int retval; @@ -175,11 +175,11 @@ getopt_long(int nargc, char ** nargv, char * options, struct option * long_optio } else current_argv_len = strlen(current_argv); - for (i = 0; long_options[i].name; i++) { + for (i = 0; long_options[i].name; i++) { if (strncmp(current_argv, long_options[i].name, current_argv_len)) continue; - if (strlen(long_options[i].name) == (unsigned)current_argv_len) { + if (strlen(long_options[i].name) == (unsigned)current_argv_len) { match = i; break; } @@ -215,7 +215,7 @@ getopt_long(int nargc, char ** nargv, char * options, struct option * long_optio if (long_options[match].flag) { *long_options[match].flag = long_options[match].val; retval = 0; - } else + } else retval = long_options[match].val; if (index) *index = match; diff --git a/fuzzing/CMakeLists.txt b/fuzzing/CMakeLists.txt new file mode 100644 index 0000000000..02fb513803 --- /dev/null +++ b/fuzzing/CMakeLists.txt @@ -0,0 +1,14 @@ +include_directories(${libheif_BINARY_DIR} ${libheif_SOURCE_DIR}/libheif ${libheif_SOURCE_DIR}/libheif/api) + + +add_executable(box_fuzzer box_fuzzer.cc) +target_link_libraries(box_fuzzer PRIVATE heif) + +add_executable(color_conversion_fuzzer color_conversion_fuzzer.cc) +target_link_libraries(color_conversion_fuzzer PRIVATE heif) + +add_executable(encoder_fuzzer encoder_fuzzer.cc) +target_link_libraries(encoder_fuzzer PRIVATE heif) + +add_executable(file_fuzzer file_fuzzer.cc) +target_link_libraries(file_fuzzer PRIVATE heif) diff --git a/fuzzing/Makefile.am b/fuzzing/Makefile.am deleted file mode 100644 index 534b10b516..0000000000 --- a/fuzzing/Makefile.am +++ /dev/null @@ -1,3 +0,0 @@ -EXTRA_DIST = \ - corpus/*.heic \ - dictionary.txt diff --git a/libheif/box_fuzzer.cc b/fuzzing/box_fuzzer.cc similarity index 63% rename from libheif/box_fuzzer.cc rename to fuzzing/box_fuzzer.cc index f1535c1bd7..114c8dc5e4 100644 --- a/libheif/box_fuzzer.cc +++ b/fuzzing/box_fuzzer.cc @@ -22,23 +22,31 @@ #include "box.h" #include "bitstream.h" +#include "context.h" #include "logging.h" extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - auto reader = std::make_shared(data, size, false); + auto reader = std::make_shared(data, size, false); - heif::BitstreamRange range(reader, size); + // --- OSS-Fuzz assumes a bug if the allocated memory exceeds 2560 MB. + // Set a lower allocation limit to prevent this. + + // Use a context for tracking the memory usage and set the reduced limit. + HeifContext ctx; + ctx.get_security_limits()->max_total_memory = UINT64_C(2) * 1024 * 1024 * 1024; + + BitstreamRange range(reader, size); for (;;) { - std::shared_ptr box; - heif::Error error = heif::Box::read(range, &box); - if (error != heif::Error::Ok || range.error()) { + std::shared_ptr box; + Error error = Box::read(range, &box, ctx.get_security_limits()); + if (error != Error::Ok || range.error()) { break; } box->get_type(); box->get_type_string(); - heif::Indent indent; + Indent indent; box->dump(indent); } diff --git a/libheif/color_conversion_fuzzer.cc b/fuzzing/color_conversion_fuzzer.cc similarity index 72% rename from libheif/color_conversion_fuzzer.cc rename to fuzzing/color_conversion_fuzzer.cc index 13509ff9ed..91d740a8ee 100644 --- a/libheif/color_conversion_fuzzer.cc +++ b/fuzzing/color_conversion_fuzzer.cc @@ -23,8 +23,8 @@ #include #include "bitstream.h" -#include "heif_colorconversion.h" -#include "heif_image.h" +#include "color-conversion/colorconversion.h" +#include "pixelimage.h" static bool is_valid_chroma(uint8_t chroma) { @@ -57,47 +57,53 @@ static bool is_valid_colorspace(uint8_t colorspace) } } -static bool read_plane(heif::BitstreamRange* range, - std::shared_ptr image, heif_channel channel, - int width, int height, int bit_depth) +static bool read_plane(BitstreamRange* range, + std::shared_ptr image, heif_channel channel, + uint32_t width, uint32_t height, int bit_depth) { if (width <= 0 || height <= 0) { return false; } - if (!range->prepare_read(static_cast(width) * height)) { + if (std::numeric_limits::max()/width/height == 0) { return false; } - if (!image->add_plane(channel, width, height, bit_depth)) { + if (!range->prepare_read(static_cast(width) * height)) { return false; } - int stride; + if (auto err = image->add_plane(channel, width, height, bit_depth, heif_get_disabled_security_limits())) { + return false; + } + size_t stride; uint8_t* plane = image->get_plane(channel, &stride); assert(stride >= width); auto stream = range->get_istream(); - for (int y = 0; y < height; y++, plane += stride) { + for (uint32_t y = 0; y < height; y++, plane += stride) { assert(stream->read(plane, width)); } return true; } -static bool read_plane_interleaved(heif::BitstreamRange* range, - std::shared_ptr image, heif_channel channel, - int width, int height, int bit_depth, int comps) +static bool read_plane_interleaved(BitstreamRange* range, + std::shared_ptr image, heif_channel channel, + uint32_t width, uint32_t height, int bit_depth, int comps) { if (width <= 0 || height <= 0) { return false; } - if (!range->prepare_read(static_cast(width) * height * comps)) { + if (std::numeric_limits::max()/width/height/comps == 0) { return false; } - if (!image->add_plane(channel, width, height, bit_depth)) { + if (!range->prepare_read(static_cast(width) * height * comps)) { return false; } - int stride; + if (auto err = image->add_plane(channel, width, height, bit_depth, heif_get_disabled_security_limits())) { + return false; + } + size_t stride; uint8_t* plane = image->get_plane(channel, &stride); assert(stride >= width * comps); auto stream = range->get_istream(); - for (int y = 0; y < height; y++, plane += stride) { + for (uint32_t y = 0; y < height; y++, plane += stride) { assert(stream->read(plane, width * comps)); } return true; @@ -105,11 +111,11 @@ static bool read_plane_interleaved(heif::BitstreamRange* range, extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { - auto reader = std::make_shared(data, size, false); - heif::BitstreamRange range(reader, size); + auto reader = std::make_shared(data, size, false); + BitstreamRange range(reader, size); - int width; - int height; + uint32_t width; + uint32_t height; int bit_depth; bool alpha; uint8_t in_chroma; @@ -147,7 +153,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) return 0; } - auto in_image = std::make_shared(); + auto in_image = std::make_shared(); in_image->create(width, height, static_cast(in_colorspace), static_cast(in_chroma)); @@ -240,15 +246,31 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) } } - auto out_image = convert_colorspace(in_image, - static_cast(out_colorspace), - static_cast(out_chroma), - nullptr); - if (!out_image) { + // TODO: also fuzz these parameters. + int output_bpp = 0; // Same as input. + heif_encoding_options* options = heif_encoding_options_alloc(); + + heif_color_conversion_options_ext* options_ext = heif_color_conversion_options_ext_alloc(); + + auto out_image_result = convert_colorspace(in_image, + static_cast(out_colorspace), + static_cast(out_chroma), + nclx_profile::undefined(), + output_bpp, + options->color_conversion_options, + options_ext, + heif_get_disabled_security_limits()); + + heif_encoding_options_free(options); + heif_color_conversion_options_ext_free(options_ext); + + if (!out_image_result) { // Conversion is not supported. return 0; } + auto out_image = *out_image_result; + assert(out_image->get_width() == width); assert(out_image->get_height() == height); assert(out_image->get_chroma_format() == diff --git a/fuzzing/corpus/clap-overflow-divide-zero.heic b/fuzzing/data/corpus/clap-overflow-divide-zero.heic similarity index 100% rename from fuzzing/corpus/clap-overflow-divide-zero.heic rename to fuzzing/data/corpus/clap-overflow-divide-zero.heic diff --git a/fuzzing/data/corpus/clusterfuzz-testcase-minimized-color-conversion-fuzzer-6275694804467712 b/fuzzing/data/corpus/clusterfuzz-testcase-minimized-color-conversion-fuzzer-6275694804467712 new file mode 100644 index 0000000000..f781bfba7d Binary files /dev/null and b/fuzzing/data/corpus/clusterfuzz-testcase-minimized-color-conversion-fuzzer-6275694804467712 differ diff --git a/fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-4616081830051840.heic b/fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-4616081830051840.heic new file mode 100644 index 0000000000..c95adbc36e Binary files /dev/null and b/fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-4616081830051840.heic differ diff --git a/fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5120279175102464.heic b/fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5120279175102464.heic new file mode 100644 index 0000000000..0fe029e93e Binary files /dev/null and b/fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5120279175102464.heic differ diff --git a/fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5643900194127872.heic b/fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5643900194127872.heic new file mode 100644 index 0000000000..b058f402d0 Binary files /dev/null and b/fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5643900194127872.heic differ diff --git a/fuzzing/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5651556035198976.heic b/fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5651556035198976.heic similarity index 100% rename from fuzzing/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5651556035198976.heic rename to fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5651556035198976.heic diff --git a/fuzzing/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5662360964956160.heic b/fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5662360964956160.heic similarity index 100% rename from fuzzing/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5662360964956160.heic rename to fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5662360964956160.heic diff --git a/fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5671864958976000.heic b/fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5671864958976000.heic new file mode 100644 index 0000000000..678997e78b Binary files /dev/null and b/fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5671864958976000.heic differ diff --git a/fuzzing/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5686319331672064.heic b/fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5686319331672064.heic similarity index 100% rename from fuzzing/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5686319331672064.heic rename to fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5686319331672064.heic diff --git a/fuzzing/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5718632116518912.heic b/fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5718632116518912.heic similarity index 100% rename from fuzzing/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5718632116518912.heic rename to fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5718632116518912.heic diff --git a/fuzzing/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5720856641142784.heic b/fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5720856641142784.heic similarity index 100% rename from fuzzing/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5720856641142784.heic rename to fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5720856641142784.heic diff --git a/fuzzing/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5724458239655936.heic b/fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5724458239655936.heic similarity index 100% rename from fuzzing/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5724458239655936.heic rename to fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5724458239655936.heic diff --git a/fuzzing/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5732616832024576.heic b/fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5732616832024576.heic similarity index 100% rename from fuzzing/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5732616832024576.heic rename to fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5732616832024576.heic diff --git a/fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5752063708495872.heic b/fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5752063708495872.heic new file mode 100644 index 0000000000..0c6b1985b6 Binary files /dev/null and b/fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5752063708495872.heic differ diff --git a/fuzzing/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5757633845264384.heic b/fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5757633845264384.heic similarity index 100% rename from fuzzing/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5757633845264384.heic rename to fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-5757633845264384.heic diff --git a/fuzzing/corpus/clusterfuzz-testcase-minimized-file-fuzzer-6045213633282048.heic b/fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-6045213633282048.heic similarity index 100% rename from fuzzing/corpus/clusterfuzz-testcase-minimized-file-fuzzer-6045213633282048.heic rename to fuzzing/data/corpus/clusterfuzz-testcase-minimized-file-fuzzer-6045213633282048.heic diff --git a/fuzzing/corpus/colors-no-alpha-thumbnail.heic b/fuzzing/data/corpus/colors-no-alpha-thumbnail.heic similarity index 100% rename from fuzzing/corpus/colors-no-alpha-thumbnail.heic rename to fuzzing/data/corpus/colors-no-alpha-thumbnail.heic diff --git a/fuzzing/corpus/colors-no-alpha.heic b/fuzzing/data/corpus/colors-no-alpha.heic similarity index 100% rename from fuzzing/corpus/colors-no-alpha.heic rename to fuzzing/data/corpus/colors-no-alpha.heic diff --git a/fuzzing/corpus/colors-with-alpha-thumbnail.heic b/fuzzing/data/corpus/colors-with-alpha-thumbnail.heic similarity index 100% rename from fuzzing/corpus/colors-with-alpha-thumbnail.heic rename to fuzzing/data/corpus/colors-with-alpha-thumbnail.heic diff --git a/fuzzing/corpus/colors-with-alpha.heic b/fuzzing/data/corpus/colors-with-alpha.heic similarity index 100% rename from fuzzing/corpus/colors-with-alpha.heic rename to fuzzing/data/corpus/colors-with-alpha.heic diff --git a/fuzzing/data/corpus/crash-20ca2625096a205937b809a7841e7f019f0b2dc6.heic b/fuzzing/data/corpus/crash-20ca2625096a205937b809a7841e7f019f0b2dc6.heic new file mode 100644 index 0000000000..d49c028be0 Binary files /dev/null and b/fuzzing/data/corpus/crash-20ca2625096a205937b809a7841e7f019f0b2dc6.heic differ diff --git a/fuzzing/corpus/github_138.heic b/fuzzing/data/corpus/github_138.heic similarity index 100% rename from fuzzing/corpus/github_138.heic rename to fuzzing/data/corpus/github_138.heic diff --git a/fuzzing/corpus/github_15.heic b/fuzzing/data/corpus/github_15.heic similarity index 100% rename from fuzzing/corpus/github_15.heic rename to fuzzing/data/corpus/github_15.heic diff --git a/fuzzing/corpus/github_20.heic b/fuzzing/data/corpus/github_20.heic similarity index 100% rename from fuzzing/corpus/github_20.heic rename to fuzzing/data/corpus/github_20.heic diff --git a/fuzzing/corpus/github_367_1.heic b/fuzzing/data/corpus/github_367_1.heic similarity index 100% rename from fuzzing/corpus/github_367_1.heic rename to fuzzing/data/corpus/github_367_1.heic diff --git a/fuzzing/corpus/github_367_2.heic b/fuzzing/data/corpus/github_367_2.heic similarity index 100% rename from fuzzing/corpus/github_367_2.heic rename to fuzzing/data/corpus/github_367_2.heic diff --git a/fuzzing/corpus/github_44.heic b/fuzzing/data/corpus/github_44.heic similarity index 100% rename from fuzzing/corpus/github_44.heic rename to fuzzing/data/corpus/github_44.heic diff --git a/fuzzing/corpus/github_45.heic b/fuzzing/data/corpus/github_45.heic similarity index 100% rename from fuzzing/corpus/github_45.heic rename to fuzzing/data/corpus/github_45.heic diff --git a/fuzzing/corpus/github_46_1.heic b/fuzzing/data/corpus/github_46_1.heic similarity index 100% rename from fuzzing/corpus/github_46_1.heic rename to fuzzing/data/corpus/github_46_1.heic diff --git a/fuzzing/corpus/github_46_2.heic b/fuzzing/data/corpus/github_46_2.heic similarity index 100% rename from fuzzing/corpus/github_46_2.heic rename to fuzzing/data/corpus/github_46_2.heic diff --git a/fuzzing/corpus/github_47.heic b/fuzzing/data/corpus/github_47.heic similarity index 100% rename from fuzzing/corpus/github_47.heic rename to fuzzing/data/corpus/github_47.heic diff --git a/fuzzing/corpus/github_48.heic b/fuzzing/data/corpus/github_48.heic similarity index 100% rename from fuzzing/corpus/github_48.heic rename to fuzzing/data/corpus/github_48.heic diff --git a/fuzzing/corpus/github_49.heic b/fuzzing/data/corpus/github_49.heic similarity index 100% rename from fuzzing/corpus/github_49.heic rename to fuzzing/data/corpus/github_49.heic diff --git a/fuzzing/corpus/github_50.heic b/fuzzing/data/corpus/github_50.heic similarity index 100% rename from fuzzing/corpus/github_50.heic rename to fuzzing/data/corpus/github_50.heic diff --git a/fuzzing/corpus/hbo_heif_context.h_126_1.heic b/fuzzing/data/corpus/hbo_heif_context.h_126_1.heic similarity index 100% rename from fuzzing/corpus/hbo_heif_context.h_126_1.heic rename to fuzzing/data/corpus/hbo_heif_context.h_126_1.heic diff --git a/fuzzing/data/corpus/j2k-siz-segment-undersized.heic b/fuzzing/data/corpus/j2k-siz-segment-undersized.heic new file mode 100644 index 0000000000..46a5702d46 Binary files /dev/null and b/fuzzing/data/corpus/j2k-siz-segment-undersized.heic differ diff --git a/fuzzing/data/corpus/region-mask-missing-refs.heic b/fuzzing/data/corpus/region-mask-missing-refs.heic new file mode 100644 index 0000000000..2d1ae588fd Binary files /dev/null and b/fuzzing/data/corpus/region-mask-missing-refs.heic differ diff --git a/fuzzing/data/corpus/rgb_generic_compressed_brotli.heic b/fuzzing/data/corpus/rgb_generic_compressed_brotli.heic new file mode 100644 index 0000000000..8771ddc811 Binary files /dev/null and b/fuzzing/data/corpus/rgb_generic_compressed_brotli.heic differ diff --git a/fuzzing/data/corpus/rgb_generic_compressed_defl.heic b/fuzzing/data/corpus/rgb_generic_compressed_defl.heic new file mode 100644 index 0000000000..acb484a43c Binary files /dev/null and b/fuzzing/data/corpus/rgb_generic_compressed_defl.heic differ diff --git a/fuzzing/data/corpus/rgb_generic_compressed_tile_deflate.heic b/fuzzing/data/corpus/rgb_generic_compressed_tile_deflate.heic new file mode 100644 index 0000000000..86c2d3e5e6 Binary files /dev/null and b/fuzzing/data/corpus/rgb_generic_compressed_tile_deflate.heic differ diff --git a/fuzzing/data/corpus/rgb_generic_compressed_zlib.heic b/fuzzing/data/corpus/rgb_generic_compressed_zlib.heic new file mode 100644 index 0000000000..98455c992d Binary files /dev/null and b/fuzzing/data/corpus/rgb_generic_compressed_zlib.heic differ diff --git a/fuzzing/data/corpus/rgb_generic_compressed_zlib_rows.heic b/fuzzing/data/corpus/rgb_generic_compressed_zlib_rows.heic new file mode 100644 index 0000000000..c0507f6cd9 Binary files /dev/null and b/fuzzing/data/corpus/rgb_generic_compressed_zlib_rows.heic differ diff --git a/fuzzing/data/corpus/rgb_generic_compressed_zlib_tiled.heic b/fuzzing/data/corpus/rgb_generic_compressed_zlib_tiled.heic new file mode 100644 index 0000000000..4b905aa465 Binary files /dev/null and b/fuzzing/data/corpus/rgb_generic_compressed_zlib_tiled.heic differ diff --git a/fuzzing/corpus/uaf_heif_context.h_117_1.heic b/fuzzing/data/corpus/uaf_heif_context.h_117_1.heic similarity index 100% rename from fuzzing/corpus/uaf_heif_context.h_117_1.heic rename to fuzzing/data/corpus/uaf_heif_context.h_117_1.heic diff --git a/fuzzing/data/corpus/uncompressed_comp_ABGR.heic b/fuzzing/data/corpus/uncompressed_comp_ABGR.heic new file mode 100644 index 0000000000..d0a01266eb Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_comp_ABGR.heic differ diff --git a/fuzzing/data/corpus/uncompressed_comp_ABGR_tiled.heic b/fuzzing/data/corpus/uncompressed_comp_ABGR_tiled.heic new file mode 100644 index 0000000000..5e9ed55bcb Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_comp_ABGR_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_comp_B16R16G16.heic b/fuzzing/data/corpus/uncompressed_comp_B16R16G16.heic new file mode 100644 index 0000000000..730106cc10 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_comp_B16R16G16.heic differ diff --git a/fuzzing/data/corpus/uncompressed_comp_B16R16G16_tiled.heic b/fuzzing/data/corpus/uncompressed_comp_B16R16G16_tiled.heic new file mode 100644 index 0000000000..34c55deb40 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_comp_B16R16G16_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_comp_M.heic b/fuzzing/data/corpus/uncompressed_comp_M.heic new file mode 100644 index 0000000000..e6d0c5424e Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_comp_M.heic differ diff --git a/fuzzing/data/corpus/uncompressed_comp_M_tiled.heic b/fuzzing/data/corpus/uncompressed_comp_M_tiled.heic new file mode 100644 index 0000000000..92db9bf779 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_comp_M_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_comp_R5G6B5_tiled.heic b/fuzzing/data/corpus/uncompressed_comp_R5G6B5_tiled.heic new file mode 100644 index 0000000000..07a67a2c5e Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_comp_R5G6B5_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_comp_R7+1G7+1B7+1_tiled.heic b/fuzzing/data/corpus/uncompressed_comp_R7+1G7+1B7+1_tiled.heic new file mode 100644 index 0000000000..d1f4771d72 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_comp_R7+1G7+1B7+1_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_comp_R7G7+1B7_tiled.heic b/fuzzing/data/corpus/uncompressed_comp_R7G7+1B7_tiled.heic new file mode 100644 index 0000000000..d3f4e26f95 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_comp_R7G7+1B7_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_comp_R7G7B7_tiled.heic b/fuzzing/data/corpus/uncompressed_comp_R7G7B7_tiled.heic new file mode 100644 index 0000000000..34e37e899e Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_comp_R7G7B7_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_comp_RGB.heic b/fuzzing/data/corpus/uncompressed_comp_RGB.heic new file mode 100644 index 0000000000..c74129d6fc Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_comp_RGB.heic differ diff --git a/fuzzing/data/corpus/uncompressed_comp_RGB_tiled.heic b/fuzzing/data/corpus/uncompressed_comp_RGB_tiled.heic new file mode 100644 index 0000000000..ab30d7f211 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_comp_RGB_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_comp_RGB_tiled_row_tile_align.heic b/fuzzing/data/corpus/uncompressed_comp_RGB_tiled_row_tile_align.heic new file mode 100644 index 0000000000..602cc850ff Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_comp_RGB_tiled_row_tile_align.heic differ diff --git a/fuzzing/data/corpus/uncompressed_comp_RGxB.heic b/fuzzing/data/corpus/uncompressed_comp_RGxB.heic new file mode 100644 index 0000000000..a6a40778cd Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_comp_RGxB.heic differ diff --git a/fuzzing/data/corpus/uncompressed_comp_RGxB_tiled.heic b/fuzzing/data/corpus/uncompressed_comp_RGxB_tiled.heic new file mode 100644 index 0000000000..3899c91c2f Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_comp_RGxB_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_comp_VUY_420.heic b/fuzzing/data/corpus/uncompressed_comp_VUY_420.heic new file mode 100644 index 0000000000..143b1f3155 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_comp_VUY_420.heic differ diff --git a/fuzzing/data/corpus/uncompressed_comp_VUY_422.heic b/fuzzing/data/corpus/uncompressed_comp_VUY_422.heic new file mode 100644 index 0000000000..53de4201ea Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_comp_VUY_422.heic differ diff --git a/fuzzing/data/corpus/uncompressed_comp_Y16U16V16_420.heic b/fuzzing/data/corpus/uncompressed_comp_Y16U16V16_420.heic new file mode 100644 index 0000000000..412ab1d039 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_comp_Y16U16V16_420.heic differ diff --git a/fuzzing/data/corpus/uncompressed_comp_Y16U16V16_422.heic b/fuzzing/data/corpus/uncompressed_comp_Y16U16V16_422.heic new file mode 100644 index 0000000000..16d71dacb4 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_comp_Y16U16V16_422.heic differ diff --git a/fuzzing/data/corpus/uncompressed_comp_YUV_420.heic b/fuzzing/data/corpus/uncompressed_comp_YUV_420.heic new file mode 100644 index 0000000000..0836a7ea6a Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_comp_YUV_420.heic differ diff --git a/fuzzing/data/corpus/uncompressed_comp_YUV_422.heic b/fuzzing/data/corpus/uncompressed_comp_YUV_422.heic new file mode 100644 index 0000000000..bf4d870095 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_comp_YUV_422.heic differ diff --git a/fuzzing/data/corpus/uncompressed_comp_YUV_tiled.heic b/fuzzing/data/corpus/uncompressed_comp_YUV_tiled.heic new file mode 100644 index 0000000000..54c0d94ce2 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_comp_YUV_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_comp_YVU_420.heic b/fuzzing/data/corpus/uncompressed_comp_YVU_420.heic new file mode 100644 index 0000000000..38afbeaab5 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_comp_YVU_420.heic differ diff --git a/fuzzing/data/corpus/uncompressed_comp_YVU_422.heic b/fuzzing/data/corpus/uncompressed_comp_YVU_422.heic new file mode 100644 index 0000000000..7e3896efc7 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_comp_YVU_422.heic differ diff --git a/fuzzing/data/corpus/uncompressed_mix_VUY_420.heic b/fuzzing/data/corpus/uncompressed_mix_VUY_420.heic new file mode 100644 index 0000000000..d13e101dac Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_mix_VUY_420.heic differ diff --git a/fuzzing/data/corpus/uncompressed_mix_VUY_422.heic b/fuzzing/data/corpus/uncompressed_mix_VUY_422.heic new file mode 100644 index 0000000000..54820e7ca8 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_mix_VUY_422.heic differ diff --git a/fuzzing/data/corpus/uncompressed_mix_Y16U16V16_420.heic b/fuzzing/data/corpus/uncompressed_mix_Y16U16V16_420.heic new file mode 100644 index 0000000000..9b24e1da12 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_mix_Y16U16V16_420.heic differ diff --git a/fuzzing/data/corpus/uncompressed_mix_Y16U16V16_422.heic b/fuzzing/data/corpus/uncompressed_mix_Y16U16V16_422.heic new file mode 100644 index 0000000000..d58817fc01 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_mix_Y16U16V16_422.heic differ diff --git a/fuzzing/data/corpus/uncompressed_mix_YUV_420.heic b/fuzzing/data/corpus/uncompressed_mix_YUV_420.heic new file mode 100644 index 0000000000..98c9425e88 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_mix_YUV_420.heic differ diff --git a/fuzzing/data/corpus/uncompressed_mix_YUV_422.heic b/fuzzing/data/corpus/uncompressed_mix_YUV_422.heic new file mode 100644 index 0000000000..1f5688c313 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_mix_YUV_422.heic differ diff --git a/fuzzing/data/corpus/uncompressed_mix_YVU_420.heic b/fuzzing/data/corpus/uncompressed_mix_YVU_420.heic new file mode 100644 index 0000000000..15770e2953 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_mix_YVU_420.heic differ diff --git a/fuzzing/data/corpus/uncompressed_mix_YVU_422.heic b/fuzzing/data/corpus/uncompressed_mix_YVU_422.heic new file mode 100644 index 0000000000..4a82cbc633 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_mix_YVU_422.heic differ diff --git a/fuzzing/data/corpus/uncompressed_pix_ABGR.heic b/fuzzing/data/corpus/uncompressed_pix_ABGR.heic new file mode 100644 index 0000000000..31bc45bbf6 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_pix_ABGR.heic differ diff --git a/fuzzing/data/corpus/uncompressed_pix_ABGR_tiled.heic b/fuzzing/data/corpus/uncompressed_pix_ABGR_tiled.heic new file mode 100644 index 0000000000..031b908105 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_pix_ABGR_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_pix_B16R16G16.heic b/fuzzing/data/corpus/uncompressed_pix_B16R16G16.heic new file mode 100644 index 0000000000..3b07cc4d78 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_pix_B16R16G16.heic differ diff --git a/fuzzing/data/corpus/uncompressed_pix_B16R16G16_tiled.heic b/fuzzing/data/corpus/uncompressed_pix_B16R16G16_tiled.heic new file mode 100644 index 0000000000..39d1695c1e Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_pix_B16R16G16_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_pix_M.heic b/fuzzing/data/corpus/uncompressed_pix_M.heic new file mode 100644 index 0000000000..2b4c44531f Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_pix_M.heic differ diff --git a/fuzzing/data/corpus/uncompressed_pix_M_tiled.heic b/fuzzing/data/corpus/uncompressed_pix_M_tiled.heic new file mode 100644 index 0000000000..bae5b75c0b Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_pix_M_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_pix_R5G6B5_tiled.heic b/fuzzing/data/corpus/uncompressed_pix_R5G6B5_tiled.heic new file mode 100644 index 0000000000..bb11120608 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_pix_R5G6B5_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_pix_R7+1G7+1B7+1_tiled.heic b/fuzzing/data/corpus/uncompressed_pix_R7+1G7+1B7+1_tiled.heic new file mode 100644 index 0000000000..c77af9b14f Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_pix_R7+1G7+1B7+1_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_pix_R7G7+1B7_tiled.heic b/fuzzing/data/corpus/uncompressed_pix_R7G7+1B7_tiled.heic new file mode 100644 index 0000000000..a2390e4386 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_pix_R7G7+1B7_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_pix_R7G7B7_tiled.heic b/fuzzing/data/corpus/uncompressed_pix_R7G7B7_tiled.heic new file mode 100644 index 0000000000..8bfbe08b6e Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_pix_R7G7B7_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_pix_R8G8B8A8_bsz0_psz10_tiled.heic b/fuzzing/data/corpus/uncompressed_pix_R8G8B8A8_bsz0_psz10_tiled.heic new file mode 100644 index 0000000000..3153cd3c6f Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_pix_R8G8B8A8_bsz0_psz10_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_pix_R8G8B8A8_bsz0_psz5_tiled.heic b/fuzzing/data/corpus/uncompressed_pix_R8G8B8A8_bsz0_psz5_tiled.heic new file mode 100644 index 0000000000..5269301d78 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_pix_R8G8B8A8_bsz0_psz5_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_pix_R8G8B8_bsz0_psz10_tiled.heic b/fuzzing/data/corpus/uncompressed_pix_R8G8B8_bsz0_psz10_tiled.heic new file mode 100644 index 0000000000..862c2b409b Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_pix_R8G8B8_bsz0_psz10_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_pix_R8G8B8_bsz0_psz5_tiled.heic b/fuzzing/data/corpus/uncompressed_pix_R8G8B8_bsz0_psz5_tiled.heic new file mode 100644 index 0000000000..17aebda4bf Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_pix_R8G8B8_bsz0_psz5_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_pix_RGB.heic b/fuzzing/data/corpus/uncompressed_pix_RGB.heic new file mode 100644 index 0000000000..dac85fca2e Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_pix_RGB.heic differ diff --git a/fuzzing/data/corpus/uncompressed_pix_RGB_tiled.heic b/fuzzing/data/corpus/uncompressed_pix_RGB_tiled.heic new file mode 100644 index 0000000000..8eda57d504 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_pix_RGB_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_pix_RGB_tiled_row_tile_align.heic b/fuzzing/data/corpus/uncompressed_pix_RGB_tiled_row_tile_align.heic new file mode 100644 index 0000000000..c2829edff8 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_pix_RGB_tiled_row_tile_align.heic differ diff --git a/fuzzing/data/corpus/uncompressed_pix_RGxB.heic b/fuzzing/data/corpus/uncompressed_pix_RGxB.heic new file mode 100644 index 0000000000..ae8b8d814a Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_pix_RGxB.heic differ diff --git a/fuzzing/data/corpus/uncompressed_pix_RGxB_tiled.heic b/fuzzing/data/corpus/uncompressed_pix_RGxB_tiled.heic new file mode 100644 index 0000000000..d964a29154 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_pix_RGxB_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_pix_YUV_tiled.heic b/fuzzing/data/corpus/uncompressed_pix_YUV_tiled.heic new file mode 100644 index 0000000000..b84f32b091 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_pix_YUV_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_row_ABGR.heic b/fuzzing/data/corpus/uncompressed_row_ABGR.heic new file mode 100644 index 0000000000..5a60fd6ec9 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_row_ABGR.heic differ diff --git a/fuzzing/data/corpus/uncompressed_row_ABGR_tiled.heic b/fuzzing/data/corpus/uncompressed_row_ABGR_tiled.heic new file mode 100644 index 0000000000..896d341b98 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_row_ABGR_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_row_B16R16G16.heic b/fuzzing/data/corpus/uncompressed_row_B16R16G16.heic new file mode 100644 index 0000000000..754d6b8af0 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_row_B16R16G16.heic differ diff --git a/fuzzing/data/corpus/uncompressed_row_B16R16G16_tiled.heic b/fuzzing/data/corpus/uncompressed_row_B16R16G16_tiled.heic new file mode 100644 index 0000000000..8f8db75e5a Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_row_B16R16G16_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_row_M.heic b/fuzzing/data/corpus/uncompressed_row_M.heic new file mode 100644 index 0000000000..130e7cee83 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_row_M.heic differ diff --git a/fuzzing/data/corpus/uncompressed_row_M_tiled.heic b/fuzzing/data/corpus/uncompressed_row_M_tiled.heic new file mode 100644 index 0000000000..05aa7f71ac Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_row_M_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_row_R5G6B5_tiled.heic b/fuzzing/data/corpus/uncompressed_row_R5G6B5_tiled.heic new file mode 100644 index 0000000000..d94e68675d Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_row_R5G6B5_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_row_R7+1G7+1B7+1_tiled.heic b/fuzzing/data/corpus/uncompressed_row_R7+1G7+1B7+1_tiled.heic new file mode 100644 index 0000000000..7b299c0a56 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_row_R7+1G7+1B7+1_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_row_R7G7+1B7_tiled.heic b/fuzzing/data/corpus/uncompressed_row_R7G7+1B7_tiled.heic new file mode 100644 index 0000000000..081b9b1c8c Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_row_R7G7+1B7_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_row_R7G7B7_tiled.heic b/fuzzing/data/corpus/uncompressed_row_R7G7B7_tiled.heic new file mode 100644 index 0000000000..943b60655b Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_row_R7G7B7_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_row_RGB.heic b/fuzzing/data/corpus/uncompressed_row_RGB.heic new file mode 100644 index 0000000000..30c440000e Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_row_RGB.heic differ diff --git a/fuzzing/data/corpus/uncompressed_row_RGB_tiled.heic b/fuzzing/data/corpus/uncompressed_row_RGB_tiled.heic new file mode 100644 index 0000000000..a5d6ebcc5a Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_row_RGB_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_row_RGB_tiled_row_tile_align.heic b/fuzzing/data/corpus/uncompressed_row_RGB_tiled_row_tile_align.heic new file mode 100644 index 0000000000..a9969bec4b Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_row_RGB_tiled_row_tile_align.heic differ diff --git a/fuzzing/data/corpus/uncompressed_row_RGxB.heic b/fuzzing/data/corpus/uncompressed_row_RGxB.heic new file mode 100644 index 0000000000..de274d5c8b Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_row_RGxB.heic differ diff --git a/fuzzing/data/corpus/uncompressed_row_RGxB_tiled.heic b/fuzzing/data/corpus/uncompressed_row_RGxB_tiled.heic new file mode 100644 index 0000000000..e438fc2268 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_row_RGxB_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_row_YUV_tiled.heic b/fuzzing/data/corpus/uncompressed_row_YUV_tiled.heic new file mode 100644 index 0000000000..7238f7fadb Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_row_YUV_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_tile_ABGR_tiled.heic b/fuzzing/data/corpus/uncompressed_tile_ABGR_tiled.heic new file mode 100644 index 0000000000..157944f4bb Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_tile_ABGR_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_tile_B16R16G16_tiled.heic b/fuzzing/data/corpus/uncompressed_tile_B16R16G16_tiled.heic new file mode 100644 index 0000000000..3937184d17 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_tile_B16R16G16_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_tile_M_tiled.heic b/fuzzing/data/corpus/uncompressed_tile_M_tiled.heic new file mode 100644 index 0000000000..07cce7eecd Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_tile_M_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_tile_R5G6B5_tiled.heic b/fuzzing/data/corpus/uncompressed_tile_R5G6B5_tiled.heic new file mode 100644 index 0000000000..8c67f301c7 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_tile_R5G6B5_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_tile_R7+1G7+1B7+1_tiled.heic b/fuzzing/data/corpus/uncompressed_tile_R7+1G7+1B7+1_tiled.heic new file mode 100644 index 0000000000..0c40460788 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_tile_R7+1G7+1B7+1_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_tile_R7G7+1B7_tiled.heic b/fuzzing/data/corpus/uncompressed_tile_R7G7+1B7_tiled.heic new file mode 100644 index 0000000000..3f001eaa12 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_tile_R7G7+1B7_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_tile_R7G7B7_tiled.heic b/fuzzing/data/corpus/uncompressed_tile_R7G7B7_tiled.heic new file mode 100644 index 0000000000..a0d51e6172 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_tile_R7G7B7_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_tile_RGB_tiled.heic b/fuzzing/data/corpus/uncompressed_tile_RGB_tiled.heic new file mode 100644 index 0000000000..d4adc501da Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_tile_RGB_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_tile_RGB_tiled_row_tile_align.heic b/fuzzing/data/corpus/uncompressed_tile_RGB_tiled_row_tile_align.heic new file mode 100644 index 0000000000..dc8f38484b Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_tile_RGB_tiled_row_tile_align.heic differ diff --git a/fuzzing/data/corpus/uncompressed_tile_RGxB_tiled.heic b/fuzzing/data/corpus/uncompressed_tile_RGxB_tiled.heic new file mode 100644 index 0000000000..68a29d1877 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_tile_RGxB_tiled.heic differ diff --git a/fuzzing/data/corpus/uncompressed_tile_YUV_tiled.heic b/fuzzing/data/corpus/uncompressed_tile_YUV_tiled.heic new file mode 100644 index 0000000000..3b965e6c32 Binary files /dev/null and b/fuzzing/data/corpus/uncompressed_tile_YUV_tiled.heic differ diff --git a/fuzzing/dictionary.txt b/fuzzing/data/dictionary.txt similarity index 100% rename from fuzzing/dictionary.txt rename to fuzzing/data/dictionary.txt diff --git a/libheif/encoder_fuzzer.cc b/fuzzing/encoder_fuzzer.cc similarity index 93% rename from libheif/encoder_fuzzer.cc rename to fuzzing/encoder_fuzzer.cc index 059ece77c7..fc9fa46b51 100644 --- a/libheif/encoder_fuzzer.cc +++ b/fuzzing/encoder_fuzzer.cc @@ -24,7 +24,7 @@ #include -#include "heif.h" +#include "libheif/heif.h" static void generate_plane(int width, int height, uint8_t* output, int stride) { @@ -151,10 +151,9 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) static const size_t kMaxEncoders = 5; const heif_encoder_descriptor* encoder_descriptors[kMaxEncoders]; - int count = heif_context_get_encoder_descriptors(context.get(), - use_avif ? heif_compression_AV1 : heif_compression_HEVC, - nullptr, - encoder_descriptors, kMaxEncoders); + int count = heif_get_encoder_descriptors(use_avif ? heif_compression_AV1 : heif_compression_HEVC, + nullptr, + encoder_descriptors, kMaxEncoders); assert(count >= 0); if (count == 0) { return 0; @@ -185,12 +184,11 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) err = heif_context_encode_image(context.get(), image, encoder, nullptr, &img); heif_image_release(image); heif_encoder_release(encoder); + heif_image_handle_release(img); if (err.code != heif_error_Ok) { return 0; } - heif_image_handle_release(img); - MemoryWriter writer; struct heif_writer w; w.writer_api_version = 1; diff --git a/libheif/file_fuzzer.cc b/fuzzing/file_fuzzer.cc similarity index 82% rename from libheif/file_fuzzer.cc rename to fuzzing/file_fuzzer.cc index 376a3060c0..13fefb60ae 100644 --- a/libheif/file_fuzzer.cc +++ b/fuzzing/file_fuzzer.cc @@ -22,7 +22,7 @@ #include #include -#include "heif.h" +#include "libheif/heif.h" static const enum heif_colorspace kFuzzColorSpace = heif_colorspace_YCbCr; static const enum heif_chroma kFuzzChroma = heif_chroma_420; @@ -30,12 +30,14 @@ static const enum heif_chroma kFuzzChroma = heif_chroma_420; static void TestDecodeImage(struct heif_context* ctx, const struct heif_image_handle* handle, size_t filesize) { - struct heif_image* image; + struct heif_image* image = nullptr; struct heif_error err; - heif_image_handle_is_primary_image(handle); + bool primary = heif_image_handle_is_primary_image(handle); + (void) primary; int width = heif_image_handle_get_width(handle); int height = heif_image_handle_get_height(handle); + (void)width; (void)height; assert(width >= 0); assert(height >= 0); int metadata_count = heif_image_handle_get_number_of_metadata_blocks(handle, nullptr); @@ -46,11 +48,16 @@ static void TestDecodeImage(struct heif_context* ctx, int metadata_ids_count = heif_image_handle_get_list_of_metadata_block_IDs(handle, nullptr, metadata_ids, metadata_count); assert(metadata_count == metadata_ids_count); + (void)metadata_ids_count; for (int i = 0; i < metadata_count; i++) { heif_image_handle_get_metadata_type(handle, metadata_ids[i]); heif_image_handle_get_metadata_content_type(handle, metadata_ids[i]); size_t metadata_size = heif_image_handle_get_metadata_size(handle, metadata_ids[i]); - assert(metadata_size < filesize); + + // This assertion is invalid. Metadata can in fact be larger than the file if there are several + // overlapping iloc extents. Does not make much sense, but it is technically valid. + //assert(metadata_size < filesize); + uint8_t* metadata_data = static_cast(malloc(metadata_size)); assert(metadata_data); heif_image_handle_get_metadata(handle, metadata_ids[i], metadata_data); @@ -60,6 +67,7 @@ static void TestDecodeImage(struct heif_context* ctx, err = heif_decode_image(handle, &image, kFuzzColorSpace, kFuzzChroma, nullptr); if (err.code != heif_error_Ok) { + heif_image_release(image); return; } @@ -80,16 +88,26 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { struct heif_context* ctx; struct heif_error err; - struct heif_image_handle* primary_handle; + struct heif_image_handle* primary_handle = nullptr; int images_count; heif_item_id* image_IDs = NULL; + bool explicit_init = size == 0 || data[size - 1] & 1; + + if (explicit_init) { + heif_init(nullptr); + } heif_check_filetype(data, clip_int(size)); heif_main_brand(data, clip_int(size)); heif_get_file_mime_type(data, clip_int(size)); ctx = heif_context_alloc(); + heif_context_get_security_limits(ctx)->max_total_memory = UINT64_C(2) * 1024 * 1024 * 1024; assert(ctx); + + auto* limits = heif_context_get_security_limits(ctx); + limits->max_memory_block_size = 128 * 1024 * 1024; // 128 MB + err = heif_context_read_from_memory(ctx, data, size, nullptr); if (err.code != heif_error_Ok) { // Not a valid HEIF file passed (which is most likely while fuzzing). @@ -101,6 +119,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) assert(heif_image_handle_is_primary_image(primary_handle)); TestDecodeImage(ctx, primary_handle, size); heif_image_handle_release(primary_handle); + primary_handle = nullptr; } images_count = heif_context_get_number_of_top_level_images(ctx); @@ -118,9 +137,10 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) } for (int i = 0; i < images_count; ++i) { - struct heif_image_handle* image_handle; + struct heif_image_handle* image_handle = nullptr; err = heif_context_get_image_handle(ctx, image_IDs[i], &image_handle); if (err.code != heif_error_Ok) { + heif_image_handle_release(image_handle); // Ignore, we are only interested in crashes here. continue; } @@ -141,7 +161,13 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) } quit: + heif_image_handle_release(primary_handle); heif_context_free(ctx); free(image_IDs); + + if (explicit_init) { + heif_deinit(); + } + return 0; } diff --git a/gdk-pixbuf/CMakeLists.txt b/gdk-pixbuf/CMakeLists.txt index 85ad597085..de67e79624 100644 --- a/gdk-pixbuf/CMakeLists.txt +++ b/gdk-pixbuf/CMakeLists.txt @@ -1,20 +1,21 @@ -if(UNIX) +if(UNIX OR MINGW) find_package(PkgConfig) - find_package(Threads) pkg_check_modules(GDKPIXBUF2 gdk-pixbuf-2.0) if(GDKPIXBUF2_FOUND) - execute_process(COMMAND ${PKG_CONFIG_EXECUTABLE} gdk-pixbuf-2.0 --variable gdk_pixbuf_moduledir --define-variable=prefix=${CMAKE_INSTALL_PREFIX} OUTPUT_VARIABLE GDKPIXBUF2_MODULE_DIR OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process( + COMMAND ${PKG_CONFIG_EXECUTABLE} gdk-pixbuf-2.0 --variable gdk_pixbuf_moduledir + --define-variable=prefix=${CMAKE_INSTALL_PREFIX} OUTPUT_VARIABLE GDKPIXBUF2_MODULE_DIR + OUTPUT_STRIP_TRAILING_WHITESPACE) add_library(pixbufloader-heif MODULE pixbufloader-heif.c) - target_include_directories(pixbufloader-heif - PRIVATE - ${GDKPIXBUF2_INCLUDE_DIRS} - ${libheif_BINARY_DIR} - ${libheif_SOURCE_DIR}) + target_include_directories(pixbufloader-heif PRIVATE ${GDKPIXBUF2_INCLUDE_DIRS} ${libheif_BINARY_DIR} ${libheif_SOURCE_DIR}/libheif/api) + + target_link_directories(pixbufloader-heif PRIVATE ${GDKPIXBUF2_LIBRARY_DIRS}) + target_link_libraries(pixbufloader-heif PUBLIC ${GDKPIXBUF2_LIBRARIES} heif) - install(TARGETS pixbufloader-heif LIBRARY DESTINATION ${GDKPIXBUF2_MODULE_DIR}) + install(TARGETS pixbufloader-heif DESTINATION ${GDKPIXBUF2_MODULE_DIR}) endif() endif() diff --git a/gdk-pixbuf/Makefile.am b/gdk-pixbuf/Makefile.am deleted file mode 100644 index c89caaa7b8..0000000000 --- a/gdk-pixbuf/Makefile.am +++ /dev/null @@ -1,14 +0,0 @@ -AUTOMAKE_OPTIONS = subdir-objects - -gdk_pixbuf_module_LTLIBRARIES = - -if HAVE_GDKPIXBUF2 -gdk_pixbuf_module_LTLIBRARIES += libpixbufloader-heif.la -libpixbufloader_heif_la_DEPENDENCIES = ../libheif/libheif.la -libpixbufloader_heif_la_CFLAGS = -I$(top_srcdir) -I$(top_builddir) $(gdkpixbuf_CFLAGS) -libpixbufloader_heif_la_LIBADD = ../libheif/libheif.la $(gdkpixbuf_LIBS) -libpixbufloader_heif_la_LDFLAGS = -avoid-version -module -libpixbufloader_heif_la_SOURCES = pixbufloader-heif.c -endif - -EXTRA_DIST = CMakeLists.txt diff --git a/gdk-pixbuf/pixbufloader-heif.c b/gdk-pixbuf/pixbufloader-heif.c index e547c4f960..9666e798d1 100644 --- a/gdk-pixbuf/pixbufloader-heif.c +++ b/gdk-pixbuf/pixbufloader-heif.c @@ -67,7 +67,7 @@ static gboolean stop_load(gpointer context, GError** error) { HeifPixbufCtx* hpc; struct heif_error err; - struct heif_context* hc; + struct heif_context* hc = NULL; struct heif_image_handle* hdl = NULL; struct heif_image* img = NULL; int width, height, stride; @@ -79,6 +79,12 @@ static gboolean stop_load(gpointer context, GError** error) result = FALSE; hpc = (HeifPixbufCtx*) context; + err = heif_init(NULL); + if (err.code != heif_error_Ok) { + g_warning("%s", err.message); + goto cleanup; + } + hc = heif_context_alloc(); if (!hc) { g_warning("cannot allocate heif_context"); @@ -130,6 +136,23 @@ static gboolean stop_load(gpointer context, GError** error) pixbuf = gdk_pixbuf_new_from_data(data, GDK_COLORSPACE_RGB, has_alpha, 8, width, height, stride, release_heif_image, img); + size_t profile_size = heif_image_handle_get_raw_color_profile_size(hdl); + if(profile_size) { + guchar *profile_data = (guchar *)g_malloc0(profile_size); + + err = heif_image_handle_get_raw_color_profile(hdl, profile_data); + if (err.code == heif_error_Ok) { + gchar *profile_base64 = g_base64_encode(profile_data, profile_size); + gdk_pixbuf_set_option(pixbuf, "icc-profile", profile_base64); + g_free(profile_base64); + } + else { + // Having no ICC profile is perfectly fine. Do not show any warning because of that. + } + + g_free(profile_data); + } + if (hpc->prepare_func) { (*hpc->prepare_func)(pixbuf, NULL, hpc->user_data); } @@ -162,6 +185,8 @@ static gboolean stop_load(gpointer context, GError** error) g_byte_array_free(hpc->data, TRUE); g_free(hpc); + heif_deinit(); + return result; } diff --git a/gnome/CMakeLists.txt b/gnome/CMakeLists.txt index e8b8bf6421..c1768cf2ee 100644 --- a/gnome/CMakeLists.txt +++ b/gnome/CMakeLists.txt @@ -1,2 +1,3 @@ -install(FILES heif.thumbnailer DESTINATION ${CMAKE_INSTALL_DATADIR}/thumbnailers) -install(FILES avif.xml heif.xml DESTINATION ${CMAKE_INSTALL_DATADIR}/mime/packages) +if(TARGET heif-thumbnailer) + install(FILES heif.thumbnailer DESTINATION ${CMAKE_INSTALL_DATADIR}/thumbnailers) +endif() diff --git a/gnome/Makefile.am b/gnome/Makefile.am deleted file mode 100644 index a9c066e5a0..0000000000 --- a/gnome/Makefile.am +++ /dev/null @@ -1,10 +0,0 @@ -thumbnailer_dir = $(datadir)/thumbnailers -thumbnailer__DATA = heif.thumbnailer - -mime_dir = $(datadir)/mime/packages -mime__DATA = heif.xml avif.xml - -EXTRA_DIST = \ - heif.thumbnailer \ - heif.xml \ - avif.xml diff --git a/gnome/avif.xml b/gnome/avif.xml deleted file mode 100644 index 5b576fe62d..0000000000 --- a/gnome/avif.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - AVIF image - AVIF-Bild - - - diff --git a/gnome/heif.xml b/gnome/heif.xml deleted file mode 100644 index d82ee83344..0000000000 --- a/gnome/heif.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - HEIF image - HEIF-Bild - - - - - diff --git a/go/Makefile.am b/go/Makefile.am deleted file mode 100644 index 329261e457..0000000000 --- a/go/Makefile.am +++ /dev/null @@ -1,26 +0,0 @@ -EXTRA_DIST = \ - heif/heif.go \ - heif/heif_test.go \ - heif/keepalive_16.go \ - heif/keepalive_17.go - -gopath: - ln -sf ${CURDIR} ${CURDIR}/src - -if HAVE_GO -test-go: gopath $(top_builddir)/libheif/libheif.la $(top_builddir)/libheif.pc - GOPATH=${CURDIR} PKG_CONFIG_PATH=$(abs_top_builddir):$(abs_top_builddir)/libde265/dist/lib/pkgconfig/ CGO_CFLAGS="-I$(abs_top_builddir)" CGO_LDFLAGS="-L$(abs_top_builddir)/libheif/.libs" LD_LIBRARY_PATH=$(abs_top_builddir)/libheif/.libs $(GO) test -v heif - -format-go: gopath - GOPATH=${CURDIR} $(GO) fmt heif -else -test-go: - echo ""go" not present in "${PATH}", skipping tests" - -format-go: - echo ""go" not present in "${PATH}", skipping formatting" -endif - -test-local: test-go - -format-local: format-go diff --git a/go/heif/heif.go b/go/heif/heif.go index d2ee918f96..c4c85bf4c2 100644 --- a/go/heif/heif.go +++ b/go/heif/heif.go @@ -1,21 +1,28 @@ /* * GO interface to libheif - * Copyright (c) 2018 struktur AG, Dirk Farin * - * This file is part of heif, an example application using libheif. + * MIT License * - * heif is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * Copyright (c) 2018 Dirk Farin + * Copyright (c) 2018 Joachim Bauch * - * heif is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * You should have received a copy of the GNU General Public License - * along with heif. If not, see . + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ package heif @@ -45,11 +52,17 @@ func GetVersion() string { type Compression C.enum_heif_compression_format const ( - CompressionUndefined = C.heif_compression_undefined - CompressionHEVC = C.heif_compression_HEVC - CompressionAV1 = C.heif_compression_AV1 - CompressionAVC = C.heif_compression_AVC - CompressionJPEG = C.heif_compression_JPEG + CompressionUndefined = C.heif_compression_undefined + CompressionHEVC = C.heif_compression_HEVC + CompressionAV1 = C.heif_compression_AV1 + CompressionAVC = C.heif_compression_AVC + CompressionJPEG = C.heif_compression_JPEG + CompressionJPEG2000 = C.heif_compression_JPEG2000 + CompressionVVC = C.heif_compression_VVC + CompressionEVC = C.heif_compression_EVC + CompressionUncompressed = C.heif_compression_uncompressed + CompressionMask = C.heif_compression_mask + CompressionHTJ2K = C.heif_compression_HTJ2K ) type Chroma C.enum_heif_chroma @@ -71,13 +84,30 @@ const ( ChromaInterleaved32Bit = C.heif_chroma_interleaved_32bit ) +type ChromaDownsamplingAlgorithm C.enum_heif_chroma_downsampling_algorithm + +const ( + ChromaDownsamplingAverage = C.heif_chroma_downsampling_average + ChromaDownsamplingNearestNeighbor = C.heif_chroma_downsampling_nearest_neighbor + ChromaDownsamplingSharpYUV = C.heif_chroma_downsampling_sharp_yuv +) + +type ChromaUpsamplingAlgorithm C.enum_heif_chroma_upsampling_algorithm + +const ( + ChromaUpsamplingNearestNeighbor = C.heif_chroma_upsampling_nearest_neighbor + ChromaUpsamplingBilinear = C.heif_chroma_upsampling_bilinear +) + type Colorspace C.enum_heif_colorspace const ( - ColorspaceUndefined = C.heif_colorspace_undefined - ColorspaceYCbCr = C.heif_colorspace_YCbCr - ColorspaceRGB = C.heif_colorspace_RGB - ColorspaceMonochrome = C.heif_colorspace_monochrome + ColorspaceUndefined = C.heif_colorspace_undefined + ColorspaceYCbCr = C.heif_colorspace_YCbCr + ColorspaceRGB = C.heif_colorspace_RGB + ColorspaceMonochrome = C.heif_colorspace_monochrome + ColorspaceFilterArray = C.heif_colorspace_filter_array + ColorspaceNonvisual = C.heif_colorspace_nonvisual ) type Channel C.enum_heif_channel @@ -91,6 +121,10 @@ const ( ChannelB = C.heif_channel_B ChannelAlpha = C.heif_channel_Alpha ChannelInterleaved = C.heif_channel_interleaved + ChannelFilterArray = C.heif_channel_filter_array + ChannelDepth = C.heif_channel_depth + ChannelDisparity = C.heif_channel_disparity + ChannelUnknown = C.heif_channel_unknown ) type ProgressStep C.enum_heif_progress_step @@ -150,8 +184,14 @@ const ( // Error during encoding or when writing to the output ErrorEncoding = C.heif_error_Encoding_error + ErrorEndOfSequence = C.heif_error_End_of_sequence + // Application has asked for a color profile type that does not exist ErrorColorProfileDoesNotExist = C.heif_error_Color_profile_does_not_exist + + ErrorPluginLoadingError = C.heif_error_Plugin_loading_error + + ErrorCanceled = C.heif_error_Canceled ) type ErrorSubcode C.enum_heif_suberror_code @@ -175,6 +215,8 @@ const ( SuberrorNoMetaBox = C.heif_suberror_No_meta_box + SuberrorNoMoovBox = C.heif_suberror_No_moov_box + SuberrorNoHdlrBox = C.heif_suberror_No_hdlr_box SuberrorNoHvcCBox = C.heif_suberror_No_hvcC_box @@ -212,6 +254,8 @@ const ( SuberrorNoAV1CBox = C.heif_suberror_No_av1C_box + SuberrorNoAVCCBox = C.heif_suberror_No_avcC_box + SuberrorInvalidCleanAperture = C.heif_suberror_Invalid_clean_aperture // Invalid specification of overlay image @@ -220,8 +264,16 @@ const ( // Overlay image completely outside of visible canvas area SuberrorOverlayImageOutsideOfCanvas = C.heif_suberror_Overlay_image_outside_of_canvas + SuberrorPluginIsNotLoaded = C.heif_suberror_Plugin_is_not_loaded + + SuberrorPluginLoadingError = C.heif_suberror_Plugin_loading_error + SuberrorAuxiliaryImageTypeUnspecified = C.heif_suberror_Auxiliary_image_type_unspecified + SuberrorCannotReadPluginDirectory = C.heif_suberror_Cannot_read_plugin_directory + + SuberrorNoMatchingDecoderInstalled = C.heif_suberror_No_matching_decoder_installed + SuberrorNoOrInvalidPrimaryItem = C.heif_suberror_No_or_invalid_primary_item SuberrorNoInfeBox = C.heif_suberror_No_infe_box @@ -234,6 +286,12 @@ const ( SuberrorInvalidImageSize = C.heif_suberror_Invalid_image_size + SuberrorCameraIntrinsicMatrixUndefined = C.heif_suberror_Camera_intrinsic_matrix_undefined + + SuberrorCameraExtrinsicMatrixUndefined = C.heif_suberror_Camera_extrinsic_matrix_undefined + + SuberrorDecompressionInvalidData = C.heif_suberror_Decompression_invalid_data + // --- Memory_allocation_error --- // A security limit preventing unreasonable memory allocations was exceeded by the input file. @@ -241,6 +299,8 @@ const ( // security limits further. SuberrorSecurityLimitExceeded = C.heif_suberror_Security_limit_exceeded + CompressionInitialisationError = C.heif_suberror_Compression_initialisation_error + // --- Usage_error --- // An item ID was used that is not present in the file. @@ -264,10 +324,32 @@ const ( // The value for the given parameter is not in the valid range. SuberrorInvalidParameterValue = C.heif_suberror_Invalid_parameter_value + SuberrorInvalidProperty = C.heif_suberror_Invalid_property + + SuberrorItemReferenceCycle = C.heif_suberror_Item_reference_cycle + SuberrorInvalidPixiBox = C.heif_suberror_Invalid_pixi_box + SuberrorInvalidRegionData = C.heif_suberror_Invalid_region_data + + SuberrorNoIspeProperty = C.heif_suberror_No_ispe_property + SuberrorWrongTileImagePixelDepth = C.heif_suberror_Wrong_tile_image_pixel_depth + SuberrorUnknownNCLXColorPrimaries = C.heif_suberror_Unknown_NCLX_color_primaries + + SuberrorUnknownNCLXTransferCharacteristics = C.heif_suberror_Unknown_NCLX_transfer_characteristics + + SuberrorUnknownNCLXMatrixCoefficients = C.heif_suberror_Unknown_NCLX_matrix_coefficients + + SuberrorInvalidJ2KCodestream = C.heif_suberror_Invalid_J2K_codestream + + SuberrorNoVcCBox = C.heif_suberror_No_vvcC_box + + SuberrorNoIcbrBox = C.heif_suberror_No_icbr_box + + SuberrorInvalidMiniBox = C.heif_suberror_Invalid_mini_box + // --- Unsupported_feature --- // Image was coded with an unsupported compression method. @@ -278,11 +360,19 @@ const ( SuberrorUnsupportedDataVersion = C.heif_suberror_Unsupported_data_version + SuberrorUnsupportedGenericCompressionMethod = C.heif_suberror_Unsupported_generic_compression_method + + SuberrorUnsupportedEssentialProperty = C.heif_suberror_Unsupported_essential_property + // The conversion of the source image to the requested chroma / colorspace is not supported. SuberrorUnsupportedColorConversion = C.heif_suberror_Unsupported_color_conversion SuberrorUnsupportedItemConstructionMethod = C.heif_suberror_Unsupported_item_construction_method + SuberrorUnsupportedHeaderCompressionMethod = C.heif_suberror_Unsupported_header_compression_method + + SubErrorUnsupportedTrackType = C.heif_suberror_Unsupported_track_type + // --- Encoder_plugin_error --- SuberrorUnsupportedBitDepth = C.heif_suberror_Unsupported_bit_depth @@ -290,6 +380,14 @@ const ( // --- Encoding_error --- SuberrorCannotWriteOutputData = C.heif_suberror_Cannot_write_output_data + + SuberrorEncoderInitialization = C.heif_suberror_Encoder_initialization + + SuberrorEncoderEncoding = C.heif_suberror_Encoder_encoding + + SuberrorEncoderCleanup = C.heif_suberror_Encoder_cleanup + + SuberrorTooManyRegions = C.heif_suberror_Too_many_regions ) type HeifError struct { @@ -350,14 +448,14 @@ func (c *Context) ReadFromFile(filename string) error { defer C.free(unsafe.Pointer(c_filename)) err := C.heif_context_read_from_file(c.context, c_filename, nil) - keepAlive(c) + runtime.KeepAlive(c) return convertHeifError(err) } func (c *Context) ReadFromMemory(data []byte) error { // TODO: Use reader API internally. err := C.heif_context_read_from_memory(c.context, unsafe.Pointer(&data[0]), C.size_t(len(data)), nil) - keepAlive(c) + runtime.KeepAlive(c) return convertHeifError(err) } @@ -377,19 +475,19 @@ func (e *Encoder) Name() string { func (e *Encoder) SetQuality(q int) error { err := C.heif_encoder_set_lossy_quality(e.encoder, C.int(q)) - keepAlive(e) + runtime.KeepAlive(e) return convertHeifError(err) } func (e *Encoder) SetLossless(l LosslessMode) error { err := C.heif_encoder_set_lossless(e.encoder, C.int(l)) - keepAlive(e) + runtime.KeepAlive(e) return convertHeifError(err) } func (e *Encoder) SetLoggingLevel(l LoggingLevel) error { err := C.heif_encoder_set_logging_level(e.encoder, C.int(l)) - keepAlive(e) + runtime.KeepAlive(e) return convertHeifError(err) } @@ -406,7 +504,7 @@ func (c *Context) convertEncoderDescriptor(d *C.struct_heif_encoder_descriptor) name: C.GoString(cname), } err := C.heif_context_get_encoder(c.context, d, &enc.encoder) - keepAlive(c) + runtime.KeepAlive(c) if err := convertHeifError(err); err != nil { return nil, err } @@ -418,7 +516,7 @@ func (c *Context) NewEncoder(compression Compression) (*Encoder, error) { const max = 1 descriptors := make([]*C.struct_heif_encoder_descriptor, max) num := int(C.heif_context_get_encoder_descriptors(c.context, uint32(compression), nil, &descriptors[0], C.int(max))) - keepAlive(c) + runtime.KeepAlive(c) if num == 0 { return nil, fmt.Errorf("no encoder for compression %v", compression) } @@ -427,39 +525,39 @@ func (c *Context) NewEncoder(compression Compression) (*Encoder, error) { func (c *Context) WriteToFile(filename string) error { err := C.heif_context_write_to_file(c.context, C.CString(filename)) - keepAlive(c) + runtime.KeepAlive(c) return convertHeifError(err) } func (c *Context) GetNumberOfTopLevelImages() int { i := int(C.heif_context_get_number_of_top_level_images(c.context)) - keepAlive(c) + runtime.KeepAlive(c) return i } func (c *Context) IsTopLevelImageID(ID int) bool { ok := C.heif_context_is_top_level_image_ID(c.context, C.heif_item_id(ID)) != 0 - keepAlive(c) + runtime.KeepAlive(c) return ok } func (c *Context) GetListOfTopLevelImageIDs() []int { num := int(C.heif_context_get_number_of_top_level_images(c.context)) - keepAlive(c) + runtime.KeepAlive(c) if num == 0 { return []int{} } origIDs := make([]C.heif_item_id, num) C.heif_context_get_list_of_top_level_image_IDs(c.context, &origIDs[0], C.int(num)) - keepAlive(c) + runtime.KeepAlive(c) return convertItemIDs(origIDs, num) } func (c *Context) GetPrimaryImageID() (int, error) { var id C.heif_item_id err := C.heif_context_get_primary_image_ID(c.context, &id) - keepAlive(c) + runtime.KeepAlive(c) if err := convertHeifError(err); err != nil { return 0, err } @@ -480,7 +578,7 @@ func freeHeifImageHandle(c *ImageHandle) { func (c *Context) GetPrimaryImageHandle() (*ImageHandle, error) { var handle ImageHandle err := C.heif_context_get_primary_image_handle(c.context, &handle.handle) - keepAlive(c) + runtime.KeepAlive(c) if err := convertHeifError(err); err != nil { return nil, err } @@ -491,7 +589,7 @@ func (c *Context) GetPrimaryImageHandle() (*ImageHandle, error) { func (c *Context) GetImageHandle(id int) (*ImageHandle, error) { var handle ImageHandle err := C.heif_context_get_image_handle(c.context, C.heif_item_id(id), &handle.handle) - keepAlive(c) + runtime.KeepAlive(c) if err := convertHeifError(err); err != nil { return nil, err } @@ -501,57 +599,57 @@ func (c *Context) GetImageHandle(id int) (*ImageHandle, error) { func (h *ImageHandle) IsPrimaryImage() bool { ok := C.heif_image_handle_is_primary_image(h.handle) != 0 - keepAlive(h) + runtime.KeepAlive(h) return ok } func (h *ImageHandle) GetWidth() int { i := int(C.heif_image_handle_get_width(h.handle)) - keepAlive(h) + runtime.KeepAlive(h) return i } func (h *ImageHandle) GetHeight() int { i := int(C.heif_image_handle_get_height(h.handle)) - keepAlive(h) + runtime.KeepAlive(h) return i } func (h *ImageHandle) HasAlphaChannel() bool { ok := C.heif_image_handle_has_alpha_channel(h.handle) != 0 - keepAlive(h) + runtime.KeepAlive(h) return ok } func (h *ImageHandle) HasDepthImage() bool { ok := C.heif_image_handle_has_depth_image(h.handle) != 0 - keepAlive(h) + runtime.KeepAlive(h) return ok } func (h *ImageHandle) GetNumberOfDepthImages() int { i := int(C.heif_image_handle_get_number_of_depth_images(h.handle)) - keepAlive(h) + runtime.KeepAlive(h) return i } func (h *ImageHandle) GetListOfDepthImageIDs() []int { num := int(C.heif_image_handle_get_number_of_depth_images(h.handle)) - keepAlive(h) + runtime.KeepAlive(h) if num == 0 { return []int{} } origIDs := make([]C.heif_item_id, num) C.heif_image_handle_get_list_of_depth_image_IDs(h.handle, &origIDs[0], C.int(num)) - keepAlive(h) + runtime.KeepAlive(h) return convertItemIDs(origIDs, num) } func (h *ImageHandle) GetDepthImageHandle(depth_image_id int) (*ImageHandle, error) { var handle ImageHandle err := C.heif_image_handle_get_depth_image_handle(h.handle, C.heif_item_id(depth_image_id), &handle.handle) - keepAlive(h) + runtime.KeepAlive(h) if err := convertHeifError(err); err != nil { return nil, err } @@ -562,27 +660,27 @@ func (h *ImageHandle) GetDepthImageHandle(depth_image_id int) (*ImageHandle, err func (h *ImageHandle) GetNumberOfThumbnails() int { i := int(C.heif_image_handle_get_number_of_thumbnails(h.handle)) - keepAlive(h) + runtime.KeepAlive(h) return i } func (h *ImageHandle) GetListOfThumbnailIDs() []int { num := int(C.heif_image_handle_get_number_of_thumbnails(h.handle)) - keepAlive(h) + runtime.KeepAlive(h) if num == 0 { return []int{} } origIDs := make([]C.heif_item_id, num) C.heif_image_handle_get_list_of_thumbnail_IDs(h.handle, &origIDs[0], C.int(num)) - keepAlive(h) + runtime.KeepAlive(h) return convertItemIDs(origIDs, num) } func (h *ImageHandle) GetThumbnail(thumbnail_id int) (*ImageHandle, error) { var handle ImageHandle err := C.heif_image_handle_get_thumbnail(h.handle, C.heif_item_id(thumbnail_id), &handle.handle) - keepAlive(h) + runtime.KeepAlive(h) runtime.SetFinalizer(&handle, freeHeifImageHandle) return &handle, convertHeifError(err) } @@ -640,7 +738,7 @@ func (h *ImageHandle) DecodeImage(colorspace Colorspace, chroma Chroma, options } err := C.heif_decode_image(h.handle, &image.image, uint32(colorspace), uint32(chroma), opt) - keepAlive(h) + runtime.KeepAlive(h) if err := convertHeifError(err); err != nil { return nil, err } @@ -651,37 +749,37 @@ func (h *ImageHandle) DecodeImage(colorspace Colorspace, chroma Chroma, options func (img *Image) GetColorspace() Colorspace { cs := Colorspace(C.heif_image_get_colorspace(img.image)) - keepAlive(img) + runtime.KeepAlive(img) return cs } func (img *Image) GetChromaFormat() Chroma { c := Chroma(C.heif_image_get_chroma_format(img.image)) - keepAlive(img) + runtime.KeepAlive(img) return c } func (img *Image) GetWidth(channel Channel) int { i := int(C.heif_image_get_width(img.image, uint32(channel))) - keepAlive(img) + runtime.KeepAlive(img) return i } func (img *Image) GetHeight(channel Channel) int { i := int(C.heif_image_get_height(img.image, uint32(channel))) - keepAlive(img) + runtime.KeepAlive(img) return i } func (img *Image) GetBitsPerPixel(channel Channel) int { i := int(C.heif_image_get_bits_per_pixel(img.image, uint32(channel))) - keepAlive(img) + runtime.KeepAlive(img) return i } func (img *Image) GetBitsPerPixelRange(channel Channel) int { i := int(C.heif_image_get_bits_per_pixel_range(img.image, uint32(channel))) - keepAlive(img) + runtime.KeepAlive(img) return i } @@ -1069,14 +1167,14 @@ func (i *ImageAccess) setData(data []byte, stride int) { func (img *Image) GetPlane(channel Channel) (*ImageAccess, error) { height := C.heif_image_get_height(img.image, uint32(channel)) - keepAlive(img) + runtime.KeepAlive(img) if height == -1 { return nil, fmt.Errorf("No such channel %v", channel) } var stride C.int plane := C.heif_image_get_plane(img.image, uint32(channel), &stride) - keepAlive(img) + runtime.KeepAlive(img) if plane == nil { return nil, fmt.Errorf("No such channel %v", channel) } @@ -1095,7 +1193,7 @@ func (img *Image) GetPlane(channel Channel) (*ImageAccess, error) { func (img *Image) NewPlane(channel Channel, width, height, depth int) (*ImageAccess, error) { err := C.heif_image_add_plane(img.image, uint32(channel), C.int(width), C.int(height), C.int(depth)) - keepAlive(img) + runtime.KeepAlive(img) if err := convertHeifError(err); err != nil { return nil, err } @@ -1105,7 +1203,7 @@ func (img *Image) NewPlane(channel Channel, width, height, depth int) (*ImageAcc func (img *Image) ScaleImage(width int, height int) (*Image, error) { var scaled_image Image err := C.heif_image_scale_image(img.image, &scaled_image.image, C.int(width), C.int(height), nil) - keepAlive(img) + runtime.KeepAlive(img) if err := convertHeifError(err); err != nil { return nil, err } @@ -1239,6 +1337,10 @@ func imageFromYCbCr(i *image.YCbCr) (*Image, error) { switch sr := i.SubsampleRatio; sr { case image.YCbCrSubsampleRatio420: cm = Chroma420 + case image.YCbCrSubsampleRatio422: + cm = Chroma422 + case image.YCbCrSubsampleRatio444: + cm = Chroma444 default: return nil, fmt.Errorf("unsupported subsample ratio: %s", sr.String()) } @@ -1256,13 +1358,23 @@ func imageFromYCbCr(i *image.YCbCr) (*Image, error) { pY.setData([]byte(i.Y), i.YStride) // TODO: Might need to be updated for other SubsampleRatio values. - halfW, halfH := (w+1)/2, (h+1)/2 - pCb, err := out.NewPlane(ChannelCb, halfW, halfH, depth) + var cw, ch int + switch cm { + case Chroma420: + cw, ch = (w+1)/2, (h+1)/2 + case Chroma444: + cw, ch = w, h + case Chroma422: + cw, ch = (w+1)/2, h + default: + return nil, fmt.Errorf("cm not support: %v", cm) + } + pCb, err := out.NewPlane(ChannelCb, cw, ch, depth) if err != nil { return nil, fmt.Errorf("failed to add Cb plane: %v", err) } pCb.setData([]byte(i.Cb), i.CStride) - pCr, err := out.NewPlane(ChannelCr, halfW, halfH, depth) + pCr, err := out.NewPlane(ChannelCr, cw, ch, depth) if err != nil { return nil, fmt.Errorf("failed to add Cr plane: %v", err) } @@ -1330,10 +1442,10 @@ func EncodeFromImage(img image.Image, compression Compression, quality int, loss var handle ImageHandle err2 := C.heif_context_encode_image(ctx.context, out.image, enc.encoder, encOpts.options, &handle.handle) - keepAlive(ctx) - keepAlive(out) - keepAlive(enc) - keepAlive(encOpts) + runtime.KeepAlive(ctx) + runtime.KeepAlive(out) + runtime.KeepAlive(enc) + runtime.KeepAlive(encOpts) if err := convertHeifError(err2); err != nil { return nil, fmt.Errorf("failed to encode image: %v", err) } diff --git a/go/heif/heif_test.go b/go/heif/heif_test.go index 8771db0eed..d48c9fb4de 100644 --- a/go/heif/heif_test.go +++ b/go/heif/heif_test.go @@ -1,21 +1,27 @@ /* - * GO interface to libheif - * Copyright (c) 2018 struktur AG, Joachim Bauch + * Test for GO interface to libheif * - * This file is part of heif, an example application using libheif. + * MIT License * - * heif is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. + * Copyright (c) 2018 Joachim Bauch * - * heif is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: * - * You should have received a copy of the GNU General Public License - * along with heif. If not, see . + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. */ package heif @@ -65,14 +71,8 @@ func CheckHeifImage(t *testing.T, handle *ImageHandle, thumbnail bool) { } } - if img, err := handle.DecodeImage(ColorspaceUndefined, ChromaUndefined, nil); err != nil { - t.Errorf("Could not decode image: %s", err) - } else { - img.GetColorspace() - img.GetChromaFormat() - } - decodeTests := []decodeTest{ + decodeTest{ColorspaceUndefined, ChromaUndefined}, decodeTest{ColorspaceYCbCr, Chroma420}, decodeTest{ColorspaceYCbCr, Chroma422}, decodeTest{ColorspaceYCbCr, Chroma444}, diff --git a/go/heif/keepalive_16.go b/go/heif/keepalive_16.go deleted file mode 100644 index ef6941d5e4..0000000000 --- a/go/heif/keepalive_16.go +++ /dev/null @@ -1,30 +0,0 @@ -// +build !go1.7 - -/* - * GO interface to libheif - * Copyright (c) 2019 struktur AG, Dirk Farin - * - * This file is part of heif, an example application using libheif. - * - * heif is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * heif is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with heif. If not, see . - */ - -package heif - -//go:noinline -func keepAlive(x interface{}) { - if _Cgo_always_false { - _Cgo_use(x) - } -} diff --git a/go/heif/keepalive_17.go b/go/heif/keepalive_17.go deleted file mode 100644 index d5b5d85a27..0000000000 --- a/go/heif/keepalive_17.go +++ /dev/null @@ -1,31 +0,0 @@ -// +build go1.7 - -/* - * GO interface to libheif - * Copyright (c) 2019 struktur AG, Dirk Farin - * - * This file is part of heif, an example application using libheif. - * - * heif is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * heif is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with heif. If not, see . - */ - -package heif - -import ( - "runtime" -) - -func keepAlive(x interface{}) { - runtime.KeepAlive(x) -} diff --git a/heifio/CMakeLists.txt b/heifio/CMakeLists.txt new file mode 100644 index 0000000000..e6b1bbf7a3 --- /dev/null +++ b/heifio/CMakeLists.txt @@ -0,0 +1,118 @@ +# Needed to find libheif/heif_version.h while compiling the library +include_directories(${libheif_BINARY_DIR} ${libheif_SOURCE_DIR}/libheif/api ${libheif_SOURCE_DIR}/libheif) + +add_library(heifio STATIC + decoder.h + decoder_raw.cc + decoder_raw.h + decoder_y4m.cc + decoder_y4m.h + encoder.h + encoder.cc + encoder_y4m.h + encoder_y4m.cc + exif.h + exif.cc + stubs.cc) + +target_link_libraries(heifio PRIVATE heif) + +set_target_properties(heifio + PROPERTIES + VERSION ${PROJECT_VERSION}) + + +target_compile_definitions(heifio + PUBLIC + LIBHEIF_EXPORTS + HAVE_VISIBILITY) + +find_package(TIFF) +if (TIFF_FOUND) + target_sources(heifio PRIVATE decoder_tiff.cc decoder_tiff.h encoder_tiff.h encoder_tiff.cc) + target_link_libraries(heifio PRIVATE TIFF::TIFF) + target_compile_definitions(heifio PUBLIC HAVE_LIBTIFF=1) + + option(WITH_GEOTIFF "Print GeoTIFF metadata when encoding tiled TIFFs (requires libgeotiff)" OFF) + if (WITH_GEOTIFF) + find_library(GEOTIFF_LIBRARY geotiff) + find_path(GEOTIFF_INCLUDE_DIR geotiff/geotiff.h) + if (GEOTIFF_LIBRARY AND GEOTIFF_INCLUDE_DIR) + set(GEOTIFF_FOUND TRUE) + target_compile_definitions(heifio PUBLIC HAVE_GEOTIFF=1) + target_link_libraries(heifio PRIVATE ${GEOTIFF_LIBRARY}) + target_include_directories(heifio PRIVATE ${GEOTIFF_INCLUDE_DIR}) + endif() + endif() +endif() + +find_package(JPEG) +if (TARGET JPEG::JPEG) + target_compile_definitions(heifio PUBLIC -DHAVE_LIBJPEG=1) + + include(CheckCXXSourceCompiles) + + # this is needed for CheckCXXSourceCompiles + set(CMAKE_REQUIRED_LIBRARIES ${JPEG_LIBRARIES}) + set(CMAKE_REQUIRED_INCLUDES ${JPEG_INCLUDE_DIRS}) + check_cxx_source_compiles(" +#include +#include +#include + +int main() { + jpeg_write_icc_profile(NULL, NULL, 0); + return 0; +} +" HAVE_JPEG_WRITE_ICC_PROFILE) + unset(CMAKE_REQUIRED_LIBRARIES) + unset(CMAKE_REQUIRED_INCLUDES) + + if (HAVE_JPEG_WRITE_ICC_PROFILE) + add_definitions(-DHAVE_JPEG_WRITE_ICC_PROFILE=1) + endif () + + target_link_libraries(heifio PRIVATE JPEG::JPEG) + + target_sources(heifio PRIVATE encoder_jpeg.cc encoder_jpeg.h) + target_sources(heifio PRIVATE decoder.h decoder_jpeg.cc decoder_jpeg.h) +endif () + + +find_package(PNG) +set(PNG_FOUND ${PNG_FOUND} PARENT_SCOPE) +if (TARGET PNG::PNG) + target_compile_definitions(heifio PUBLIC -DHAVE_LIBPNG=1) + + target_link_libraries(heifio PRIVATE PNG::PNG) + + target_sources(heifio PRIVATE encoder_png.cc encoder_png.h) + target_sources(heifio PRIVATE decoder_png.cc decoder_png.h) +endif () + +message("") +message("=== Active input formats for heif-enc ===") +if (JPEG_FOUND) + message("JPEG: active") +else () + message("JPEG: ------ (libjpeg not found)") +endif () +if (PNG_FOUND) + message("PNG: active") +else () + message("PNG: ------ (libpng not found)") +endif () +if (TIFF_FOUND) + message("TIFF: active") + if (NOT WITH_GEOTIFF) + message("GeoTIFF: ------ (WITH_GEOTIFF=OFF)") + elseif (GEOTIFF_FOUND) + message("GeoTIFF: active") + else () + message("GeoTIFF: ------ (libgeotiff not found)") + endif () +else () + message("TIFF: ------ (libtiff not found)") +endif () +message("") + diff --git a/heifio/decoder.h b/heifio/decoder.h new file mode 100644 index 0000000000..5e80d25047 --- /dev/null +++ b/heifio/decoder.h @@ -0,0 +1,42 @@ +/* + libheif example application "heif". + + MIT License + + Copyright (c) 2023 Dirk Farin + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#ifndef LIBHEIF_DECODER_H +#define LIBHEIF_DECODER_H + +#include "libheif/heif.h" +#include +#include + +struct InputImage +{ + std::shared_ptr image; + std::vector xmp; + std::vector exif; + heif_orientation orientation = heif_orientation_normal; +}; + +#endif //LIBHEIF_DECODER_H diff --git a/heifio/decoder_jpeg.cc b/heifio/decoder_jpeg.cc new file mode 100644 index 0000000000..f602461c12 --- /dev/null +++ b/heifio/decoder_jpeg.cc @@ -0,0 +1,570 @@ +/* + libheif example application "heif". + + MIT License + + Copyright (c) 2023 Dirk Farin + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "decoder_jpeg.h" +#include "exif.h" + +extern "C" { +// Prevent duplicate definition for libjpeg-turbo v2.0 +// Note: these 'undef's are only a workaround for a libjpeg-turbo-v2.0 bug and +// should be removed again later. Bug has been fixed in libjpeg-turbo-v2.0.1. +#include +#if defined(LIBJPEG_TURBO_VERSION_NUMBER) && LIBJPEG_TURBO_VERSION_NUMBER == 2000000 +#undef HAVE_STDDEF_H +#undef HAVE_STDLIB_H +#endif +#include +} + +#define JPEG_EXIF_MARKER (JPEG_APP0+1) /* JPEG marker code for EXIF */ +#define JPEG_EXIF_MARKER_LEN 6 // "Exif/0/0" +#define JPEG_XMP_MARKER (JPEG_APP0+1) /* JPEG marker code for XMP */ +#define JPEG_XMP_MARKER_ID "http://ns.adobe.com/xap/1.0/" +#define JPEG_ICC_MARKER (JPEG_APP0+2) /* JPEG marker code for ICC */ +#define JPEG_ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */ + +static struct heif_error heif_error_ok = {heif_error_Ok, heif_suberror_Unspecified, "Success"}; + +static bool JPEGMarkerIsIcc(jpeg_saved_marker_ptr marker) +{ + return + marker->marker == JPEG_ICC_MARKER && + marker->data_length >= JPEG_ICC_OVERHEAD_LEN && + /* verify the identifying string */ + GETJOCTET(marker->data[0]) == 0x49 && + GETJOCTET(marker->data[1]) == 0x43 && + GETJOCTET(marker->data[2]) == 0x43 && + GETJOCTET(marker->data[3]) == 0x5F && + GETJOCTET(marker->data[4]) == 0x50 && + GETJOCTET(marker->data[5]) == 0x52 && + GETJOCTET(marker->data[6]) == 0x4F && + GETJOCTET(marker->data[7]) == 0x46 && + GETJOCTET(marker->data[8]) == 0x49 && + GETJOCTET(marker->data[9]) == 0x4C && + GETJOCTET(marker->data[10]) == 0x45 && + GETJOCTET(marker->data[11]) == 0x0; +} + +bool ReadICCProfileFromJPEG(j_decompress_ptr cinfo, + JOCTET** icc_data_ptr, + unsigned int* icc_data_len) +{ + jpeg_saved_marker_ptr marker; + int num_markers = 0; + int seq_no; + JOCTET* icc_data; + unsigned int total_length; +#define MAX_SEQ_NO 255 /* sufficient since marker numbers are bytes */ + char marker_present[MAX_SEQ_NO + 1]; /* 1 if marker found */ + unsigned int data_length[MAX_SEQ_NO + 1]; /* size of profile data in marker */ + unsigned int data_offset[MAX_SEQ_NO + 1]; /* offset for data in marker */ + + *icc_data_ptr = NULL; /* avoid confusion if FALSE return */ + *icc_data_len = 0; + + /* This first pass over the saved markers discovers whether there are + * any ICC markers and verifies the consistency of the marker numbering. + */ + + for (seq_no = 1; seq_no <= MAX_SEQ_NO; seq_no++) + marker_present[seq_no] = 0; + + for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) { + if (JPEGMarkerIsIcc(marker)) { + if (num_markers == 0) + num_markers = GETJOCTET(marker->data[13]); + else if (num_markers != GETJOCTET(marker->data[13])) + return FALSE; /* inconsistent num_markers fields */ + seq_no = GETJOCTET(marker->data[12]); + if (seq_no <= 0 || seq_no > num_markers) + return FALSE; /* bogus sequence number */ + if (marker_present[seq_no]) + return FALSE; /* duplicate sequence numbers */ + marker_present[seq_no] = 1; + data_length[seq_no] = marker->data_length - JPEG_ICC_OVERHEAD_LEN; + } + } + + if (num_markers == 0) + return FALSE; + + /* Check for missing markers, count total space needed, + * compute offset of each marker's part of the data. + */ + + total_length = 0; + for (seq_no = 1; seq_no <= num_markers; seq_no++) { + if (marker_present[seq_no] == 0) + return FALSE; /* missing sequence number */ + data_offset[seq_no] = total_length; + total_length += data_length[seq_no]; + } + + if (total_length <= 0) + return FALSE; /* found only empty markers? */ + + /* Allocate space for assembled data */ + icc_data = (JOCTET*) malloc(total_length * sizeof(JOCTET)); + if (icc_data == NULL) + return FALSE; /* oops, out of memory */ + + /* and fill it in */ + for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) { + if (JPEGMarkerIsIcc(marker)) { + JOCTET FAR* src_ptr; + JOCTET* dst_ptr; + unsigned int length; + seq_no = GETJOCTET(marker->data[12]); + dst_ptr = icc_data + data_offset[seq_no]; + src_ptr = marker->data + JPEG_ICC_OVERHEAD_LEN; + length = data_length[seq_no]; + while (length--) { + *dst_ptr++ = *src_ptr++; + } + } + } + + *icc_data_ptr = icc_data; + *icc_data_len = total_length; + + return TRUE; +} + + +static bool JPEGMarkerIsXMP(jpeg_saved_marker_ptr marker) +{ + return + marker->marker == JPEG_XMP_MARKER && + marker->data_length >= strlen(JPEG_XMP_MARKER_ID) + 1 && + strncmp((const char*) (marker->data), JPEG_XMP_MARKER_ID, strlen(JPEG_XMP_MARKER_ID)) == 0; +} + +bool ReadXMPFromJPEG(j_decompress_ptr cinfo, + std::vector& xmpData) +{ + jpeg_saved_marker_ptr marker; + + for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) { + if (JPEGMarkerIsXMP(marker)) { + int length = (int) (marker->data_length - (strlen(JPEG_XMP_MARKER_ID) + 1)); + xmpData.resize(length); + memcpy(xmpData.data(), marker->data + strlen(JPEG_XMP_MARKER_ID) + 1, length); + return true; + } + } + + return false; +} + + +static bool JPEGMarkerIsEXIF(jpeg_saved_marker_ptr marker) +{ + return marker->marker == JPEG_EXIF_MARKER && + marker->data_length >= JPEG_EXIF_MARKER_LEN && + GETJOCTET(marker->data[0]) == 'E' && + GETJOCTET(marker->data[1]) == 'x' && + GETJOCTET(marker->data[2]) == 'i' && + GETJOCTET(marker->data[3]) == 'f' && + GETJOCTET(marker->data[4]) == 0 && + GETJOCTET(marker->data[5]) == 0; +} + +bool ReadEXIFFromJPEG(j_decompress_ptr cinfo, + std::vector& exifData) +{ + jpeg_saved_marker_ptr marker; + + for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) { + if (JPEGMarkerIsEXIF(marker)) { + int length = (int) (marker->data_length - JPEG_EXIF_MARKER_LEN); + exifData.resize(length); + memcpy(exifData.data(), marker->data + JPEG_EXIF_MARKER_LEN, length); + return true; + } + } + + return false; +} + + +#if JPEG_LIB_VERSION < 70 +#define DCT_h_scaled_size DCT_scaled_size +#define DCT_v_scaled_size DCT_scaled_size +#endif + + +heif_error loadJPEG(const char *filename, InputImage *input_image) +{ + struct heif_image* image = nullptr; + struct heif_error err = heif_error_ok; + + // ### Code copied from LibVideoGfx and slightly modified to use HeifPixelImage + + struct jpeg_decompress_struct cinfo; + struct jpeg_error_mgr jerr; + + // to store embedded icc profile + uint32_t iccLen; + uint8_t* iccBuffer = NULL; + + std::vector xmpData; + std::vector exifData; + + // open input file + + FILE* infile; + if ((infile = fopen(filename, "rb")) == NULL) { + struct heif_error err = { + .code = heif_error_Invalid_input, + .subcode = heif_suberror_Unspecified, + .message = "Cannot open JPEG file"}; + return err; + } + + + // initialize decompressor + + jpeg_create_decompress(&cinfo); + + cinfo.err = jpeg_std_error(&jerr); + jpeg_stdio_src(&cinfo, infile); + + /* Adding this part to prepare for icc profile reading. */ + jpeg_save_markers(&cinfo, JPEG_ICC_MARKER, 0xFFFF); + jpeg_save_markers(&cinfo, JPEG_XMP_MARKER, 0xFFFF); + jpeg_save_markers(&cinfo, JPEG_EXIF_MARKER, 0xFFFF); + + jpeg_read_header(&cinfo, TRUE); + + bool embeddedIccFlag = ReadICCProfileFromJPEG(&cinfo, &iccBuffer, &iccLen); + bool embeddedXMPFlag = ReadXMPFromJPEG(&cinfo, xmpData); + if (embeddedXMPFlag) { + input_image->xmp = xmpData; + } + + bool embeddedEXIFFlag = ReadEXIFFromJPEG(&cinfo, exifData); + if (embeddedEXIFFlag) { + input_image->exif = exifData; + input_image->orientation = (heif_orientation) read_exif_orientation_tag(exifData.data(), (int) exifData.size()); + } + + if (cinfo.jpeg_color_space == JCS_GRAYSCALE) { + cinfo.out_color_space = JCS_GRAYSCALE; + + jpeg_start_decompress(&cinfo); + + JSAMPARRAY buffer; + buffer = (*cinfo.mem->alloc_sarray) + ((j_common_ptr) &cinfo, JPOOL_IMAGE, cinfo.output_width * cinfo.output_components, 1); + + + // create destination image + + err = heif_image_create(cinfo.output_width, cinfo.output_height, + heif_colorspace_monochrome, + heif_chroma_monochrome, + &image); + if (err.code) { + goto cleanup; + } + + err = heif_image_add_plane(image, heif_channel_Y, cinfo.output_width, cinfo.output_height, 8); + if (err.code) { goto cleanup; } + + size_t y_stride; + uint8_t* py = heif_image_get_plane2(image, heif_channel_Y, &y_stride); + + + // read the image + + while (cinfo.output_scanline < cinfo.output_height) { + (void) jpeg_read_scanlines(&cinfo, buffer, 1); + + memcpy(py + (cinfo.output_scanline - 1) * y_stride, *buffer, cinfo.output_width); + } + } + else if (cinfo.jpeg_color_space == JCS_YCbCr) { + cinfo.out_color_space = JCS_YCbCr; + + bool read_raw = false; + heif_chroma output_chroma = heif_chroma_420; + + if (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) { + + if (cinfo.comp_info[0].h_samp_factor == 1 && + cinfo.comp_info[0].v_samp_factor == 1) { + output_chroma = heif_chroma_444; + read_raw = true; + } + else if (cinfo.comp_info[0].h_samp_factor == 2 && + cinfo.comp_info[0].v_samp_factor == 1) { + output_chroma = heif_chroma_422; + read_raw = true; + } + else if (cinfo.comp_info[0].h_samp_factor == 2 && + cinfo.comp_info[0].v_samp_factor == 2) { + output_chroma = heif_chroma_420; + read_raw = true; + } + } + + int cw=0,ch=0; + switch (output_chroma) { + case heif_chroma_420: + cw = (cinfo.image_width + 1) / 2; + ch = (cinfo.image_height + 1) / 2; + break; + case heif_chroma_422: + cw = (cinfo.image_width + 1) / 2; + ch = cinfo.image_height; + break; + case heif_chroma_444: + cw = cinfo.image_width; + ch = cinfo.image_height; + break; + default: + assert(false); + } + + //read_raw = false; + + cinfo.raw_data_out = boolean(read_raw); + + jpeg_start_decompress(&cinfo); + + + // create destination image + + struct heif_error err = heif_image_create(cinfo.output_width, cinfo.output_height, + heif_colorspace_YCbCr, + output_chroma, + &image); + if (err.code) { goto cleanup; } + + err = heif_image_add_plane(image, heif_channel_Y, cinfo.output_width, cinfo.output_height, 8); + if (err.code) { goto cleanup; } + + err = heif_image_add_plane(image, heif_channel_Cb, cw, ch, 8); + if (err.code) { goto cleanup; } + + err = heif_image_add_plane(image, heif_channel_Cr, cw, ch, 8); + if (err.code) { goto cleanup; } + + size_t stride[3]; + uint8_t* p[3]; + p[0] = heif_image_get_plane2(image, heif_channel_Y, &stride[0]); + p[1] = heif_image_get_plane2(image, heif_channel_Cb, &stride[1]); + p[2] = heif_image_get_plane2(image, heif_channel_Cr, &stride[2]); + + // read the image + + if (read_raw) { + // adapted from https://github.com/AOMediaCodec/libavif/blob/430ea2df584dcb95ff1632c17643ebbbb2f3bc81/apps/shared/avifjpeg.c + + JSAMPIMAGE buffer; + buffer = (JSAMPIMAGE)(cinfo.mem->alloc_small)((j_common_ptr)&cinfo, JPOOL_IMAGE, sizeof(JSAMPARRAY) * cinfo.num_components); + + // lines of output image to be read per jpeg_read_raw_data call + int readLines = 0; + // lines of samples to be read per call (for each channel) + int linesPerCall[3] = { 0, 0, 0 }; + // expected count of sample lines (for each channel) + int targetRead[3] = { 0, 0, 0 }; + for (int i = 0; i < cinfo.num_components; ++i) { + jpeg_component_info * comp = &cinfo.comp_info[i]; + + linesPerCall[i] = comp->v_samp_factor * comp->DCT_v_scaled_size; + targetRead[i] = comp->downsampled_height; + buffer[i] = (cinfo.mem->alloc_sarray)((j_common_ptr)&cinfo, + JPOOL_IMAGE, + comp->width_in_blocks * comp->DCT_h_scaled_size, + linesPerCall[i]); + readLines = std::max(readLines, linesPerCall[i]); + } + + // count of already-read lines (for each channel) + int alreadyRead[3] = { 0, 0, 0 }; + int width[3] = { (int)cinfo.output_width, cw, cw}; + + std::array targetChannel{0,1,2}; + + if (cinfo.jpeg_color_space == JCS_RGB) { + targetChannel = {2, 0, 1}; + } + else if (cinfo.jpeg_color_space == JCS_YCbCr || + cinfo.jpeg_color_space == JCS_GRAYSCALE) { + targetChannel = {0, 1, 2}; + } + else { + return heif_error{heif_error_Unsupported_filetype, + heif_suberror_Unsupported_image_type, + "JPEG with unsupported colorspace"}; + } + + while (cinfo.output_scanline < cinfo.output_height) { + jpeg_read_raw_data(&cinfo, buffer, readLines); + + int workComponents = 3; + + for (int i = 0; i < workComponents; ++i) { + int linesRead = std::min(targetRead[i] - alreadyRead[i], linesPerCall[i]); + + for (int j = 0; j < linesRead; ++j) { + memcpy(p[targetChannel[i]] + ((size_t)stride[targetChannel[i]]) * (alreadyRead[i] + j), + buffer[i][j], + width[targetChannel[i]]); + } + alreadyRead[i] += linesPerCall[i]; + } + } + } + else { + JSAMPARRAY buffer; + buffer = (*cinfo.mem->alloc_sarray) + ((j_common_ptr) &cinfo, JPOOL_IMAGE, cinfo.output_width * cinfo.output_components, 1); + + while (cinfo.output_scanline < cinfo.output_height) { + JOCTET* bufp; + + (void) jpeg_read_scanlines(&cinfo, buffer, 1); + + bufp = buffer[0]; + + size_t y = cinfo.output_scanline - 1; + + for (unsigned int x = 0; x < cinfo.output_width; x += 2) { + p[0][y * stride[0] + x] = *bufp++; + p[1][y / 2 * stride[1] + x / 2] = *bufp++; + p[2][y / 2 * stride[2] + x / 2] = *bufp++; + + if (x + 1 < cinfo.output_width) { + p[0][y * stride[0] + x + 1] = *bufp++; + } + + bufp += 2; + } + + + if (cinfo.output_scanline < cinfo.output_height) { + (void) jpeg_read_scanlines(&cinfo, buffer, 1); + + bufp = buffer[0]; + + y = cinfo.output_scanline - 1; + + for (unsigned int x = 0; x < cinfo.output_width; x++) { + p[0][y * stride[0] + x] = *bufp++; + bufp += 2; + } + } + } + } + } + else if (cinfo.jpeg_color_space == JCS_CMYK || cinfo.jpeg_color_space == JCS_YCCK) { + // libjpeg converts YCCK to CMYK internally when out_color_space is JCS_CMYK + cinfo.out_color_space = JCS_CMYK; + + jpeg_start_decompress(&cinfo); + + JSAMPARRAY buffer; + buffer = (*cinfo.mem->alloc_sarray) + ((j_common_ptr) &cinfo, JPOOL_IMAGE, cinfo.output_width * 4, 1); + + // create interleaved RGB destination image + + err = heif_image_create(cinfo.output_width, cinfo.output_height, + heif_colorspace_RGB, + heif_chroma_interleaved_RGB, + &image); + if (err.code) { goto cleanup; } + + err = heif_image_add_plane(image, heif_channel_interleaved, cinfo.output_width, cinfo.output_height, 8); + if (err.code) { goto cleanup; } + + size_t rgb_stride; + uint8_t* pRGB = heif_image_get_plane2(image, heif_channel_interleaved, &rgb_stride); + + while (cinfo.output_scanline < cinfo.output_height) { + (void) jpeg_read_scanlines(&cinfo, buffer, 1); + + JOCTET* bufp = buffer[0]; + size_t y = cinfo.output_scanline - 1; + uint8_t* row = pRGB + y * rgb_stride; + + for (unsigned int x = 0; x < cinfo.output_width; x++) { + uint8_t C = bufp[0]; + uint8_t M = bufp[1]; + uint8_t Y = bufp[2]; + uint8_t K = bufp[3]; + bufp += 4; + + // CMYK to RGB — JPEG CMYK uses inverted values (255 = no ink) + row[0] = (uint8_t)(C * K / 255); + row[1] = (uint8_t)(M * K / 255); + row[2] = (uint8_t)(Y * K / 255); + row += 3; + } + } + } + else { + jpeg_destroy_decompress(&cinfo); + free(iccBuffer); + fclose(infile); + err = {heif_error_Unsupported_feature, + heif_suberror_Unsupported_color_conversion, + "Unsupported JPEG color space"}; + return err; + } + + if (embeddedIccFlag && iccLen > 0) { + heif_image_set_raw_color_profile(image, "prof", iccBuffer, (size_t) iccLen); + } + + input_image->image = std::shared_ptr(image, + [](heif_image* img) { heif_image_release(img); }); + + // cleanup +cleanup: + free(iccBuffer); + jpeg_finish_decompress(&cinfo); + jpeg_destroy_decompress(&cinfo); + + fclose(infile); + + return err; +} diff --git a/heifio/decoder_jpeg.h b/heifio/decoder_jpeg.h new file mode 100644 index 0000000000..fd717604d9 --- /dev/null +++ b/heifio/decoder_jpeg.h @@ -0,0 +1,35 @@ +/* + libheif example application "heif". + + MIT License + + Copyright (c) 2023 Dirk Farin + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#ifndef LIBHEIF_DECODER_JPEG_H +#define LIBHEIF_DECODER_JPEG_H + +#include "decoder.h" + +LIBHEIF_API +heif_error loadJPEG(const char *filename, InputImage *input_image); + +#endif //LIBHEIF_DECODER_JPEG_H diff --git a/heifio/decoder_png.cc b/heifio/decoder_png.cc new file mode 100644 index 0000000000..3dbf1e562a --- /dev/null +++ b/heifio/decoder_png.cc @@ -0,0 +1,453 @@ +/* + libheif example application "heif". + + MIT License + + Copyright (c) 2023 Dirk Farin + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#include +#include +#include +#include +#include +#include "decoder_png.h" +#include "exif.h" + +extern "C" { +#include +} + +static struct heif_error heif_error_ok = {heif_error_Ok, heif_suberror_Unspecified, "Success"}; + +static void +user_read_fn(png_structp png_ptr, png_bytep data, png_size_t length) +{ + FILE* fh = (FILE*) png_get_io_ptr(png_ptr); + size_t n = fread((char*) data, length, 1, fh); + (void) n; +} // user_read_data + + +heif_error loadPNG(const char* filename, int output_bit_depth, InputImage *input_image) +{ + FILE* fh = fopen(filename, "rb"); + if (!fh) { + struct heif_error err = { + .code = heif_error_Invalid_input, + .subcode = heif_suberror_Unspecified, + .message = "Cannot open PNG file"}; + return err; + } + + // ### Code copied from LibVideoGfx and slightly modified to use HeifPixelImage + + struct heif_image* image = nullptr; + + png_structp png_ptr; + png_infop info_ptr; + png_uint_32 width, height; + int bit_depth, color_type, interlace_type; + int compression_type; + png_charp name; +#if (PNG_LIBPNG_VER < 10500) + png_charp png_profile_data; +#else + png_bytep png_profile_data; +#endif + uint8_t* profile_data = nullptr; + png_uint_32 profile_length = 5; + + /* Create and initialize the png_struct with the desired error handler + * functions. If you want to use the default stderr and longjump method, + * you can supply NULL for the last three parameters. We also supply the + * the compiler header file version, so that we know if the application + * was compiled with a compatible version of the library. REQUIRED + */ + png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); + assert(png_ptr != NULL); + + /* Allocate/initialize the memory for image information. REQUIRED. */ + info_ptr = png_create_info_struct(png_ptr); + if (info_ptr == NULL) { + png_destroy_read_struct(&png_ptr, (png_infopp) NULL, (png_infopp) NULL); + assert(false); // , "could not create info_ptr"); + } // if + + /* Set error handling if you are using the setjmp/longjmp method (this is + * the normal method of doing things with libpng). REQUIRED unless you + * set up your own error handlers in the png_create_read_struct() earlier. + */ + if (setjmp(png_jmpbuf(png_ptr))) { + /* Free all of the memory associated with the png_ptr and info_ptr */ + png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL); + /* If we get here, we had a problem reading the file */ + assert(false); // , "fatal error in png library"); + } // if + + /* If you are using replacement read functions, instead of calling + * png_init_io() here you would call: */ + png_set_read_fn(png_ptr, (void*) fh, user_read_fn); + /* where user_io_ptr is a structure you want available to the callbacks */ + + /* The call to png_read_info() gives us all of the information from the + * PNG file before the first IDAT (image data chunk). REQUIRED + */ + png_read_info(png_ptr, info_ptr); + + png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, + &interlace_type, NULL, NULL); + + if (png_get_valid(png_ptr, info_ptr, PNG_INFO_iCCP)) { + if (PNG_INFO_iCCP == + png_get_iCCP(png_ptr, info_ptr, &name, &compression_type, &png_profile_data, &profile_length) && + profile_length > 0) { + profile_data = (uint8_t*) malloc(profile_length); + if (profile_data) { + memcpy(profile_data, png_profile_data, profile_length); + } + } + } + /**** Set up the data transformations you want. Note that these are all + **** optional. Only call them if you want/need them. Many of the + **** transformations only work on specific types of images, and many + **** are mutually exclusive. + ****/ + + // \TODO + // /* Strip alpha bytes from the input data without combining with the + // * background (not recommended). + // */ + // png_set_strip_alpha(png_ptr); + + /* Extract multiple pixels with bit depths of 1, 2, and 4 from a single + * byte into separate bytes (useful for paletted and grayscale images). + */ + png_set_packing(png_ptr); + + + /* Expand paletted colors into true RGB triplets */ + if (color_type == PNG_COLOR_TYPE_PALETTE) { + png_set_palette_to_rgb(png_ptr); + bit_depth = 8; + } + + /* Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel */ + if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8) { + png_set_expand_gray_1_2_4_to_8(png_ptr); + bit_depth = 8; + } + + /* Set the background color to draw transparent and alpha images over. + * It is possible to set the red, green, and blue components directly + * for paletted images instead of supplying a palette index. Note that + * even if the PNG file supplies a background, you are not required to + * use it - you should use the (solid) application background if it has one. + */ + +#if 0 + // \TODO 0 is index in color lookup table - correct? used already? + png_color_16 my_background = {0, 255, 255, 255, 255}; + png_color_16 *image_background; + + if (png_get_bKGD(png_ptr, info_ptr, &image_background)) + png_set_background(png_ptr, image_background, PNG_BACKGROUND_GAMMA_FILE, 1, 1.0); + else + png_set_background(png_ptr, &my_background, PNG_BACKGROUND_GAMMA_SCREEN, 0, 1.0); +#endif + + + /* Optional call to gamma correct and add the background to the palette + * and update info structure. REQUIRED if you are expecting libpng to + * update the palette for you (ie you selected such a transform above). + */ + png_read_update_info(png_ptr, info_ptr); + + /* Allocate the memory to hold the image using the fields of info_ptr. */ + + /* The easiest way to read the image: */ + uint8_t** row_pointers = new png_bytep[height]; + assert(row_pointers != NULL); + + for (uint32_t y = 0; y < height; y++) { + row_pointers[y] = (png_bytep) malloc(png_get_rowbytes(png_ptr, info_ptr)); + assert(row_pointers[y] != NULL); + } // for + + /* Now it's time to read the image. One of these methods is REQUIRED */ + png_read_image(png_ptr, row_pointers); + + /* read rest of file, and get additional chunks in info_ptr - REQUIRED */ + png_read_end(png_ptr, info_ptr); + + + // --- read EXIF data + +#ifdef PNG_eXIf_SUPPORTED + png_bytep exifPtr = nullptr; + png_uint_32 exifSize = 0; + if (png_get_eXIf_1(png_ptr, info_ptr, &exifSize, &exifPtr) == PNG_INFO_eXIf) { + input_image->exif.resize(exifSize); + memcpy(input_image->exif.data(), exifPtr, exifSize); + + // remove the EXIF orientation since it is informal only in PNG and we do not want to confuse with an orientation not matching irot/imir + modify_exif_orientation_tag_if_it_exists(input_image->exif.data(), (int) input_image->exif.size(), 1); + } +#endif + + // --- read XMP data + +#ifdef PNG_iTXt_SUPPORTED + png_textp textPtr = nullptr; + const png_uint_32 nTextChunks = png_get_text(png_ptr, info_ptr, &textPtr, nullptr); + for (png_uint_32 i = 0; i < nTextChunks; i++, textPtr++) { + png_size_t textLength = textPtr->text_length; + if ((textPtr->compression == PNG_ITXT_COMPRESSION_NONE) || (textPtr->compression == PNG_ITXT_COMPRESSION_zTXt)) { + textLength = textPtr->itxt_length; + } + + if (!strcmp(textPtr->key, "XML:com.adobe.xmp")) { + if (textLength == 0) { + // TODO: error + } + else { + input_image->xmp.resize(textLength); + memcpy(input_image->xmp.data(), textPtr->text, textLength); + } + } + } +#endif + + int band = png_get_channels(png_ptr, info_ptr); + + /* clean up after the read, and free any memory allocated - REQUIRED */ + png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL); + + + struct heif_error err; + + bool has_alpha = (band == 2 || band == 4); + + if (band == 1 && bit_depth == 8) { + err = heif_image_create((int) width, (int) height, + heif_colorspace_monochrome, + heif_chroma_monochrome, + &image); + (void) err; + + heif_image_add_plane(image, heif_channel_Y, (int) width, (int) height, 8); + + size_t y_stride; + size_t a_stride; + uint8_t* py = heif_image_get_plane2(image, heif_channel_Y, &y_stride); + uint8_t* pa = nullptr; + + if (has_alpha) { + heif_image_add_plane(image, heif_channel_Alpha, (int) width, (int) height, 8); + + pa = heif_image_get_plane2(image, heif_channel_Alpha, &a_stride); + } + + + for (uint32_t y = 0; y < height; y++) { + uint8_t* p = row_pointers[y]; + + if (has_alpha) { + for (uint32_t x = 0; x < width; x++) { + py[y * y_stride + x] = *p++; + pa[y * a_stride + x] = *p++; + } + } + else { + memcpy(&py[y * y_stride], p, width); + } + } + } + else if (band == 1) { + assert(bit_depth > 8); + + err = heif_image_create((int) width, (int) height, + heif_colorspace_monochrome, + heif_chroma_monochrome, + &image); + (void) err; + + int bdShift = 16 - output_bit_depth; + + heif_image_add_plane(image, heif_channel_Y, (int) width, (int) height, output_bit_depth); + + size_t y_stride; + size_t a_stride = 0; + uint16_t* py = (uint16_t*) heif_image_get_plane2(image, heif_channel_Y, &y_stride); + uint16_t* pa = nullptr; + + if (has_alpha) { + heif_image_add_plane(image, heif_channel_Alpha, (int) width, (int) height, output_bit_depth); + + pa = (uint16_t*) heif_image_get_plane2(image, heif_channel_Alpha, &a_stride); + } + + y_stride /= 2; + a_stride /= 2; + + for (uint32_t y = 0; y < height; y++) { + uint8_t* p = row_pointers[y]; + + if (has_alpha) { + for (uint32_t x = 0; x < width; x++) { + uint16_t vp = (uint16_t) (((p[0] << 8) | p[1]) >> bdShift); + uint16_t va = (uint16_t) (((p[2] << 8) | p[3]) >> bdShift); + + py[x + y * y_stride] = vp; + pa[x + y * y_stride] = va; + + p += 4; + } + } + else { + for (uint32_t x = 0; x < width; x++) { + uint16_t vp = (uint16_t) (((p[0] << 8) | p[1]) >> bdShift); + + py[x + y * y_stride] = vp; + + p += 2; + } + } + } + } + else if (band == 2 && bit_depth==8) { + err = heif_image_create((int) width, (int) height, + heif_colorspace_monochrome, + heif_chroma_monochrome, + &image); + (void) err; + + heif_image_add_plane(image, heif_channel_Y, (int) width, (int) height, 8); + heif_image_add_plane(image, heif_channel_Alpha, (int) width, (int) height, 8); + + size_t stride; + uint8_t* p = heif_image_get_plane2(image, heif_channel_Y, &stride); + + size_t strideA; + uint8_t* pA = heif_image_get_plane2(image, heif_channel_Alpha, &strideA); + + for (uint32_t y = 0; y < height; y++) { + for (uint32_t x = 0; x < width; x++) { + p[y * stride + x] = row_pointers[y][2 * x]; + pA[y * strideA + x] = row_pointers[y][2 * x + 1]; + } + } + } + else if (bit_depth == 8) { + err = heif_image_create((int) width, (int) height, + heif_colorspace_RGB, + has_alpha ? heif_chroma_interleaved_RGBA : heif_chroma_interleaved_RGB, + &image); + (void) err; + + heif_image_add_plane(image, heif_channel_interleaved, (int) width, (int) height, + has_alpha ? 32 : 24); + + size_t stride; + uint8_t* p = heif_image_get_plane2(image, heif_channel_interleaved, &stride); + + for (uint32_t y = 0; y < height; y++) { + if (has_alpha) { + memcpy(p + y * stride, row_pointers[y], width * 4); + } + else { + memcpy(p + y * stride, row_pointers[y], width * 3); + } + } + } + else { + if (output_bit_depth == 8) { + err = heif_image_create((int) width, (int) height, + heif_colorspace_RGB, + has_alpha ? + heif_chroma_interleaved_RGBA : + heif_chroma_interleaved_RGB, + &image); + } + else { + err = heif_image_create((int) width, (int) height, + heif_colorspace_RGB, + has_alpha ? + heif_chroma_interleaved_RRGGBBAA_LE : + heif_chroma_interleaved_RRGGBB_LE, + &image); + } + (void) err; + + int bdShift = 16 - output_bit_depth; + + heif_image_add_plane(image, heif_channel_interleaved, (int) width, (int) height, output_bit_depth); + + size_t stride; + uint8_t* p_out = (uint8_t*) heif_image_get_plane2(image, heif_channel_interleaved, &stride); + + if (output_bit_depth==8) { + // convert HDR to SDR + + for (uint32_t y = 0; y < height; y++) { + uint8_t* p = row_pointers[y]; + + uint32_t nVal = (has_alpha ? 4 : 3) * width; + + for (uint32_t x = 0; x < nVal; x++) { + p_out[x + y * stride] = p[0]; + p+=2; + } + } + } + else { + for (uint32_t y = 0; y < height; y++) { + uint8_t* p = row_pointers[y]; + + uint32_t nVal = (has_alpha ? 4 : 3) * width; + + for (uint32_t x = 0; x < nVal; x++) { + uint16_t v = (uint16_t) (((p[0] << 8) | p[1]) >> bdShift); + p_out[2 * x + y * stride + 1] = (uint8_t) (v >> 8); + p_out[2 * x + y * stride + 0] = (uint8_t) (v & 0xFF); + p += 2; + } + } + } + } + + if (profile_data && profile_length > 0) { + heif_image_set_raw_color_profile(image, "prof", profile_data, (size_t) profile_length); + } + + free(profile_data); + for (uint32_t y = 0; y < height; y++) { + free(row_pointers[y]); + } // for + + delete[] row_pointers; + fclose(fh); + + input_image->image = std::shared_ptr(image, + [](heif_image* img) { heif_image_release(img); }); + + return heif_error_ok; +} diff --git a/heifio/decoder_png.h b/heifio/decoder_png.h new file mode 100644 index 0000000000..811df0d5be --- /dev/null +++ b/heifio/decoder_png.h @@ -0,0 +1,35 @@ +/* + libheif example application "heif". + + MIT License + + Copyright (c) 2023 Dirk Farin + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#ifndef LIBHEIF_DECODER_PNG_H +#define LIBHEIF_DECODER_PNG_H + +#include "decoder.h" + +LIBHEIF_API +heif_error loadPNG(const char* filename, int output_bit_depth, InputImage *input_image); + +#endif //LIBHEIF_DECODER_PNG_H diff --git a/heifio/decoder_raw.cc b/heifio/decoder_raw.cc new file mode 100644 index 0000000000..4ee0cce420 --- /dev/null +++ b/heifio/decoder_raw.cc @@ -0,0 +1,220 @@ +/* + libheif example application "heif". + + MIT License + + Copyright (c) 2026 Dirk Farin + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#include "decoder_raw.h" +#include +#include +#include +#include +#include +#include +#include +#include + +static struct heif_error heif_error_ok = {heif_error_Ok, heif_suberror_Unspecified, "Success"}; + + +bool parse_raw_pixel_type(const std::string& type_string, + heif_channel_datatype* out_datatype, + int* out_bit_depth) +{ + if (type_string == "uint8") { + *out_datatype = heif_channel_datatype_unsigned_integer; + *out_bit_depth = 8; + } + else if (type_string == "sint8") { + *out_datatype = heif_channel_datatype_signed_integer; + *out_bit_depth = 8; + } + else if (type_string == "uint16") { + *out_datatype = heif_channel_datatype_unsigned_integer; + *out_bit_depth = 16; + } + else if (type_string == "sint16") { + *out_datatype = heif_channel_datatype_signed_integer; + *out_bit_depth = 16; + } + else if (type_string == "uint32") { + *out_datatype = heif_channel_datatype_unsigned_integer; + *out_bit_depth = 32; + } + else if (type_string == "sint32") { + *out_datatype = heif_channel_datatype_signed_integer; + *out_bit_depth = 32; + } + else if (type_string == "float32") { + *out_datatype = heif_channel_datatype_floating_point; + *out_bit_depth = 32; + } + else if (type_string == "float64") { + *out_datatype = heif_channel_datatype_floating_point; + *out_bit_depth = 64; + } + else { + return false; + } + return true; +} + + +static void byte_swap_buffer(uint8_t* data, size_t num_elements, int bytes_per_element) +{ + for (size_t i = 0; i < num_elements; i++) { + uint8_t* elem = data + i * bytes_per_element; + for (int lo = 0, hi = bytes_per_element - 1; lo < hi; lo++, hi--) { + std::swap(elem[lo], elem[hi]); + } + } +} + + +heif_error loadRAW(const char* filename, const RawImageParameters& params, InputImage* input_image) +{ + if (params.width < 0 || params.height < 0) { + return {heif_error_Invalid_input, heif_suberror_Unspecified, + "Raw image width and height must not be negative"}; + } + + if (params.width == 0 && params.height == 0) { + return {heif_error_Invalid_input, heif_suberror_Unspecified, + "At least one of --raw-width or --raw-height must be specified"}; + } + + if (params.datatype == heif_channel_datatype_undefined || params.bit_depth <= 0) { + return {heif_error_Invalid_input, heif_suberror_Unspecified, + "Raw image pixel type must be specified (use --raw-type)"}; + } + + int bytes_per_pixel = params.bit_depth / 8; + + // Open file and get size + FILE* fp = fopen(filename, "rb"); + if (!fp) { + return {heif_error_Invalid_input, heif_suberror_Unspecified, + "Cannot open raw input file"}; + } + + fseek(fp, 0, SEEK_END); + long file_size = ftell(fp); + fseek(fp, 0, SEEK_SET); + + if (file_size <= 0) { + fclose(fp); + return {heif_error_Invalid_input, heif_suberror_Unspecified, + "Raw input file is empty or unreadable"}; + } + + // Compute missing dimension from file size + int width = params.width; + int height = params.height; + + if (width == 0) { + size_t row_bytes = static_cast(height) * bytes_per_pixel; + if (static_cast(file_size) % row_bytes != 0) { + fclose(fp); + return {heif_error_Invalid_input, heif_suberror_Unspecified, + "Raw file size is not divisible by height * bytes_per_pixel"}; + } + width = static_cast(static_cast(file_size) / row_bytes); + } + else if (height == 0) { + size_t row_bytes = static_cast(width) * bytes_per_pixel; + if (static_cast(file_size) % row_bytes != 0) { + fclose(fp); + return {heif_error_Invalid_input, heif_suberror_Unspecified, + "Raw file size is not divisible by width * bytes_per_pixel"}; + } + height = static_cast(static_cast(file_size) / row_bytes); + } + + size_t expected_size = static_cast(width) * height * bytes_per_pixel; + + if (static_cast(file_size) != expected_size) { + fclose(fp); + return {heif_error_Invalid_input, heif_suberror_Unspecified, + "Raw file size does not match width * height * bytes_per_pixel"}; + } + + // Read entire file + std::vector buffer(expected_size); + size_t n = fread(buffer.data(), 1, expected_size, fp); + fclose(fp); + + if (n != expected_size) { + return {heif_error_Invalid_input, heif_suberror_Unspecified, + "Failed to read raw input file"}; + } + + // Byte-swap from big-endian to host byte order if needed + if (params.big_endian && bytes_per_pixel > 1) { + if constexpr (std::endian::native == std::endian::little) { + byte_swap_buffer(buffer.data(), static_cast(width) * height, bytes_per_pixel); + } + } + + // Create image with nonvisual colorspace for typed component API + struct heif_image* image = nullptr; + heif_error err = heif_image_create(width, height, + heif_colorspace_nonvisual, + heif_chroma_undefined, + &image); + if (err.code != heif_error_Ok) { + return err; + } + + uint32_t component_idx = 0; + err = heif_image_add_component(image, width, height, + heif_uncompressed_component_type_monochrome, + params.datatype, params.bit_depth, + &component_idx); + if (err.code != heif_error_Ok) { + heif_image_release(image); + return err; + } + + // Get writable pointer and copy data row by row + size_t stride = 0; + uint8_t* plane = heif_image_get_component(image, component_idx, &stride); + if (!plane) { + heif_image_release(image); + return {heif_error_Invalid_input, heif_suberror_Unspecified, + "Failed to get component plane from image"}; + } + + size_t row_bytes = static_cast(width) * bytes_per_pixel; + size_t stride_bytes = stride; + + for (int y = 0; y < height; y++) { + memcpy(plane + y * stride_bytes, + buffer.data() + y * row_bytes, + row_bytes); + } + + input_image->image = std::shared_ptr(image, + [](heif_image* img) { heif_image_release(img); }); + + return heif_error_ok; +} diff --git a/heifio/decoder_raw.h b/heifio/decoder_raw.h new file mode 100644 index 0000000000..b272e67d0e --- /dev/null +++ b/heifio/decoder_raw.h @@ -0,0 +1,51 @@ +/* + libheif example application "heif". + + MIT License + + Copyright (c) 2026 Dirk Farin + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#ifndef LIBHEIF_DECODER_RAW_H +#define LIBHEIF_DECODER_RAW_H + +#include "decoder.h" +#include +#include + +struct RawImageParameters { + int width = 0; + int height = 0; + heif_channel_datatype datatype = heif_channel_datatype_undefined; + int bit_depth = 0; + bool big_endian = false; +}; + +// Maps a CLI string like "uint16" or "float32" to datatype + bit_depth. +// Returns false if the string is not recognized. +bool parse_raw_pixel_type(const std::string& type_string, + heif_channel_datatype* out_datatype, + int* out_bit_depth); + +LIBHEIF_API +heif_error loadRAW(const char* filename, const RawImageParameters& params, InputImage* input_image); + +#endif //LIBHEIF_DECODER_RAW_H diff --git a/heifio/decoder_tiff.cc b/heifio/decoder_tiff.cc new file mode 100644 index 0000000000..f37464b13c --- /dev/null +++ b/heifio/decoder_tiff.cc @@ -0,0 +1,1944 @@ +/* + libheif example application "heif". + + MIT License + + Copyright (c) 2024 Joachim Bauch + Copyright (c) 2026 Dirk Farin + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#include +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +} + +#if HAVE_GEOTIFF +#include +#include +#include +#include +#endif + +#include "decoder_tiff.h" +#include "libheif/heif_uncompressed.h" + +static heif_error heif_error_ok = {heif_error_Ok, heif_suberror_Unspecified, "Success"}; + +// Forward declarations for YCbCr helpers (defined after validateTiffFormat) +static YCbCrInfo getYCbCrInfo(TIFF* tif); +static heif_chroma ycbcrChroma(const YCbCrInfo& ycbcr); +static void deinterleaveYCbCr(const uint8_t* src, uint32_t block_w, uint32_t block_h, + uint32_t actual_w, uint32_t actual_h, + uint16_t horiz_sub, uint16_t vert_sub, + uint8_t* y_plane, size_t y_stride, + uint8_t* cb_plane, size_t cb_stride, + uint8_t* cr_plane, size_t cr_stride); + +// Forward declarations for YCbCr helpers (defined after validateTiffFormat) +static YCbCrInfo getYCbCrInfo(TIFF* tif); +static heif_chroma ycbcrChroma(const YCbCrInfo& ycbcr); +static void deinterleaveYCbCr(const uint8_t* src, uint32_t block_w, uint32_t block_h, + uint32_t actual_w, uint32_t actual_h, + uint16_t horiz_sub, uint16_t vert_sub, + uint8_t* y_plane, size_t y_stride, + uint8_t* cb_plane, size_t cb_stride, + uint8_t* cr_plane, size_t cr_stride); + +static bool seekTIFF(TIFF* tif, toff_t offset, int whence) { + TIFFSeekProc seekProc = TIFFGetSeekProc(tif); + if (!seekProc) { + return false; + } + + thandle_t handle = TIFFClientdata(tif); + if (!handle) { + return false; + } + + return seekProc(handle, offset, whence) != static_cast(-1); +} + +static bool readTIFF(TIFF* tif, void* dest, size_t size) { + TIFFReadWriteProc readProc = TIFFGetReadProc(tif); + if (!readProc) { + return false; + } + + thandle_t handle = TIFFClientdata(tif); + if (!handle) { + return false; + } + + tmsize_t result = readProc(handle, dest, size); + if (result < 0 || static_cast(result) != size) { + return false; + } + + return true; +} + +static bool readTIFFUint16(TIFF* tif, uint16_t* dest) { + if (!readTIFF(tif, dest, 2)) { + return false; + } + + if (TIFFIsByteSwapped(tif)) { + TIFFSwabShort(dest); + } + return true; +} + +static bool readTIFFUint32(TIFF* tif, uint32_t* dest) { + if (!readTIFF(tif, dest, 4)) { + return false; + } + + if (TIFFIsByteSwapped(tif)) { + TIFFSwabLong(dest); + } + return true; +} + +class ExifTags { + public: + ~ExifTags() = default; + + void Encode(std::vector* dest); + + static std::unique_ptr Parse(TIFF* tif); + + private: + class Tag { + public: + uint16_t tag; + uint16_t type; + uint32_t len; + + uint32_t offset; + std::vector data; + }; + + ExifTags(uint16_t count); + void writeUint16(std::vector* dest, uint16_t value); + void writeUint32(std::vector* dest, uint32_t value); + void writeUint32(std::vector* dest, size_t pos, uint32_t value); + void writeData(std::vector* dest, const std::vector& value); + + std::vector> tags_; +}; + +ExifTags::ExifTags(uint16_t count) { + tags_.reserve(count); +} + +// static +std::unique_ptr ExifTags::Parse(TIFF* tif) { + toff_t exif_offset; + if (!TIFFGetField(tif, TIFFTAG_EXIFIFD, &exif_offset)) { + // Image doesn't contain EXIF data. + return nullptr; + } + + if (!seekTIFF(tif, exif_offset, SEEK_SET)) { + return nullptr; + } + + uint16_t count; + if (!readTIFFUint16(tif, &count)) { + return nullptr; + } + + if (count == 0) { + return nullptr; + } + + std::unique_ptr tags(new ExifTags(count)); + for (uint16_t i = 0; i < count; i++) { + std::unique_ptr tag(new Tag()); + if (!readTIFFUint16(tif, &tag->tag)) { + return nullptr; + } + + if (!readTIFFUint16(tif, &tag->type) || tag->type > TIFF_IFD8) { + return nullptr; + } + + if (TIFFDataWidth(static_cast(tag->type)) == 0) { + return nullptr; + } + + if (!readTIFFUint32(tif, &tag->len)) { + return nullptr; + } + + if (!readTIFFUint32(tif, &tag->offset)) { + return nullptr; + } + + tags->tags_.push_back(std::move(tag)); + } + + for (const auto& tag : tags->tags_) { + size_t size = tag->len * TIFFDataWidth(static_cast(tag->type)); + if (size <= 4) { + continue; + } + + if (!seekTIFF(tif, tag->offset, SEEK_SET)) { + return nullptr; + } + + tag->data.resize(size); + if (!readTIFF(tif, tag->data.data(), size)) { + return nullptr; + } + } + + return tags; +} + +void ExifTags::writeUint16(std::vector* dest, uint16_t value) { + dest->resize(dest->size() + sizeof(value)); + void* d = dest->data() + dest->size() - sizeof(value); + memcpy(d, &value, sizeof(value)); +} + +void ExifTags::writeUint32(std::vector* dest, uint32_t value) { + dest->resize(dest->size() + sizeof(value)); + writeUint32(dest, dest->size() - sizeof(value), value); +} + +void ExifTags::writeUint32(std::vector* dest, size_t pos, uint32_t value) { + void* d = dest->data() + pos; + memcpy(d, &value, sizeof(value)); +} + +void ExifTags::writeData(std::vector* dest, const std::vector& value) { + dest->resize(dest->size() + value.size()); + void* d = dest->data() + dest->size() - value.size(); + memcpy(d, value.data(), value.size()); +} + +void ExifTags::Encode(std::vector* dest) { + if (tags_.empty()) { + return; + } + +#if HOST_BIGENDIAN + dest->push_back('M'); + dest->push_back('M'); +#else + dest->push_back('I'); + dest->push_back('I'); +#endif + writeUint16(dest, 42); + // Offset of IFD0. + writeUint32(dest, 8); + + writeUint16(dest, static_cast(tags_.size())); + for (const auto& tag : tags_) { + writeUint16(dest, tag->tag); + writeUint16(dest, tag->type); + writeUint32(dest, tag->len); + writeUint32(dest, tag->offset); + } + // No IFD1 dictionary. + writeUint32(dest, 0); + + // Update offsets of tags that have their data stored separately. + for (size_t i = 0; i < tags_.size(); i++) { + const auto& tag = tags_[i]; + size_t size = tag->data.size(); + if (size <= 4) { + continue; + } + + // StartOfTags + (TagIndex * sizeof(Tag)) + OffsetOfTagData + size_t pos = 10 + (i * 12) + 8; + size_t offset = dest->size(); + writeUint32(dest, pos, static_cast(offset)); + writeData(dest, tag->data); + } +} + +static heif_chroma get_heif_chroma(uint16_t outSpp, int output_bit_depth) +{ + if (outSpp == 1) { + return heif_chroma_monochrome; + } + if (output_bit_depth > 8) { +#if IS_BIG_ENDIAN + return (outSpp == 4) ? heif_chroma_interleaved_RRGGBBAA_BE : heif_chroma_interleaved_RRGGBB_BE; +#else + return (outSpp == 4) ? heif_chroma_interleaved_RRGGBBAA_LE : heif_chroma_interleaved_RRGGBB_LE; +#endif + } + return (outSpp == 4) ? heif_chroma_interleaved_RGBA : heif_chroma_interleaved_RGB; +} + + +heif_error getImageWidthAndHeight(TIFF *tif, uint32_t &width, uint32_t &height) +{ + if (!TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width) || + !TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height)) + { + struct heif_error err = { + .code = heif_error_Invalid_input, + .subcode = heif_suberror_Unspecified, + .message = "Can not read width and/or height from TIFF image."}; + return err; + } + + if (width == 0 || height == 0) { + return {heif_error_Invalid_input, heif_suberror_Unspecified, "Zero TIFF image size is invalid."}; + } + + return heif_error_ok; +} + +heif_error readMono(TIFF *tif, uint16_t bps, int output_bit_depth, heif_image **image) +{ + uint32_t width, height; + heif_error err = getImageWidthAndHeight(tif, width, height); + if (err.code != heif_error_Ok) { + return err; + } + err = heif_image_create((int) width, (int) height, heif_colorspace_monochrome, heif_chroma_monochrome, image); + if (err.code != heif_error_Ok) { + return err; + } + + if (bps <= 8) { + heif_image_add_plane(*image, heif_channel_Y, (int)width, (int)height, 8); + + size_t y_stride; + uint8_t *py = heif_image_get_plane2(*image, heif_channel_Y, &y_stride); + for (uint32_t row = 0; row < height; row++) { + if (TIFFReadScanline(tif, py, row, 0) < 0) { + heif_image_release(*image); + *image = nullptr; + return {heif_error_Invalid_input, heif_suberror_Unspecified, "Failed to read TIFF scanline"}; + } + py += y_stride; + } + } + else { + heif_image_add_plane(*image, heif_channel_Y, (int)width, (int)height, output_bit_depth); + + size_t y_stride; + uint8_t *py = heif_image_get_plane2(*image, heif_channel_Y, &y_stride); + int bdShift = 16 - output_bit_depth; + tdata_t buf = _TIFFmalloc(TIFFScanlineSize(tif)); + + if (output_bit_depth <= 8) { + for (uint32_t row = 0; row < height; row++) { + if (TIFFReadScanline(tif, buf, row, 0) < 0) { + _TIFFfree(buf); + heif_image_release(*image); + *image = nullptr; + return {heif_error_Invalid_input, heif_suberror_Unspecified, "Failed to read TIFF scanline"}; + } + uint16_t* src = static_cast(buf); + uint8_t* dst = py + row * y_stride; + for (uint32_t x = 0; x < width; x++) { + dst[x] = static_cast(src[x] >> 8); + } + } + } + else { + for (uint32_t row = 0; row < height; row++) { + if (TIFFReadScanline(tif, buf, row, 0) < 0) { + _TIFFfree(buf); + heif_image_release(*image); + *image = nullptr; + return {heif_error_Invalid_input, heif_suberror_Unspecified, "Failed to read TIFF scanline"}; + } + uint16_t* src = static_cast(buf); + uint16_t* dst = reinterpret_cast(py + row * y_stride); + for (uint32_t x = 0; x < width; x++) { + dst[x] = static_cast(src[x] >> bdShift); + } + } + } + _TIFFfree(buf); + } + return heif_error_ok; +} + +heif_error readPixelInterleaveRGB(TIFF *tif, uint16_t samplesPerPixel, bool hasAlpha, uint16_t bps, int output_bit_depth, heif_image **image) +{ + uint32_t width, height; + heif_error err = getImageWidthAndHeight(tif, width, height); + if (err.code != heif_error_Ok) { + return err; + } + + // --- YCbCr strip path: TIFFReadScanline doesn't work with packed YCbCr --- + YCbCrInfo ycbcr = getYCbCrInfo(tif); + if (ycbcr.is_ycbcr) { + if (bps != 8) { + return {heif_error_Unsupported_feature, heif_suberror_Unspecified, + "Only 8-bit YCbCr TIFF is supported."}; + } + + heif_chroma chroma = ycbcrChroma(ycbcr); + err = heif_image_create((int)width, (int)height, heif_colorspace_YCbCr, chroma, image); + if (err.code != heif_error_Ok) return err; + + heif_image_add_plane(*image, heif_channel_Y, (int)width, (int)height, 8); + uint32_t chroma_w = (width + ycbcr.horiz_sub - 1) / ycbcr.horiz_sub; + uint32_t chroma_h = (height + ycbcr.vert_sub - 1) / ycbcr.vert_sub; + heif_image_add_plane(*image, heif_channel_Cb, (int)chroma_w, (int)chroma_h, 8); + heif_image_add_plane(*image, heif_channel_Cr, (int)chroma_w, (int)chroma_h, 8); + + size_t y_stride, cb_stride, cr_stride; + uint8_t* y_plane = heif_image_get_plane2(*image, heif_channel_Y, &y_stride); + uint8_t* cb_plane = heif_image_get_plane2(*image, heif_channel_Cb, &cb_stride); + uint8_t* cr_plane = heif_image_get_plane2(*image, heif_channel_Cr, &cr_stride); + + uint32_t rows_per_strip = 0; + TIFFGetFieldDefaulted(tif, TIFFTAG_ROWSPERSTRIP, &rows_per_strip); + if (rows_per_strip == 0) rows_per_strip = height; + + tmsize_t strip_size = TIFFStripSize(tif); + std::vector strip_buf(strip_size); + + uint32_t n_strips = TIFFNumberOfStrips(tif); + for (uint32_t s = 0; s < n_strips; s++) { + tmsize_t read = TIFFReadEncodedStrip(tif, s, strip_buf.data(), strip_size); + if (read < 0) { + heif_image_release(*image); + *image = nullptr; + return {heif_error_Invalid_input, heif_suberror_Unspecified, "Failed to read TIFF strip"}; + } + + uint32_t strip_y = s * rows_per_strip; + uint32_t actual_h = std::min(rows_per_strip, height - strip_y); + // Packed YCbCr block dimensions must be multiples of subsampling factors + uint32_t block_w = ((width + ycbcr.horiz_sub - 1) / ycbcr.horiz_sub) * ycbcr.horiz_sub; + uint32_t block_h = ((actual_h + ycbcr.vert_sub - 1) / ycbcr.vert_sub) * ycbcr.vert_sub; + + deinterleaveYCbCr(strip_buf.data(), block_w, block_h, width, actual_h, + ycbcr.horiz_sub, ycbcr.vert_sub, + y_plane + strip_y * y_stride, y_stride, + cb_plane + (strip_y / ycbcr.vert_sub) * cb_stride, cb_stride, + cr_plane + (strip_y / ycbcr.vert_sub) * cr_stride, cr_stride); + } + + return heif_error_ok; + } + + uint16_t outSpp = (samplesPerPixel == 4 && !hasAlpha) ? 3 : samplesPerPixel; + + if (bps <= 8) { + heif_chroma chroma = get_heif_chroma(outSpp, 8); + err = heif_image_create((int)width, (int)height, heif_colorspace_RGB, chroma, image); + if (err.code != heif_error_Ok) { + return err; + } + heif_channel channel = heif_channel_interleaved; + heif_image_add_plane(*image, channel, (int)width, (int)height, outSpp * 8); + + size_t y_stride; + uint8_t *py = heif_image_get_plane2(*image, channel, &y_stride); + tdata_t buf = _TIFFmalloc(TIFFScanlineSize(tif)); + for (uint32_t row = 0; row < height; row++) { + if (TIFFReadScanline(tif, buf, row, 0) < 0) { + _TIFFfree(buf); + heif_image_release(*image); + *image = nullptr; + return {heif_error_Invalid_input, heif_suberror_Unspecified, "Failed to read TIFF scanline"}; + } + uint8_t* src = static_cast(buf); + if (outSpp == samplesPerPixel) { + memcpy(py, src, width * outSpp); + } + else { + for (uint32_t x = 0; x < width; x++) { + memcpy(py + x * outSpp, src + x * samplesPerPixel, outSpp); + } + } + py += y_stride; + } + _TIFFfree(buf); + } + else { + heif_chroma chroma = get_heif_chroma(outSpp, output_bit_depth); + err = heif_image_create((int)width, (int)height, heif_colorspace_RGB, chroma, image); + if (err.code != heif_error_Ok) { + return err; + } + heif_channel channel = heif_channel_interleaved; + int planeBitDepth = (output_bit_depth <= 8) ? outSpp * 8 : output_bit_depth; + heif_image_add_plane(*image, channel, (int)width, (int)height, planeBitDepth); + + size_t y_stride; + uint8_t *py = heif_image_get_plane2(*image, channel, &y_stride); + int bdShift = 16 - output_bit_depth; + tdata_t buf = _TIFFmalloc(TIFFScanlineSize(tif)); + + if (output_bit_depth <= 8) { + for (uint32_t row = 0; row < height; row++) { + if (TIFFReadScanline(tif, buf, row, 0) < 0) { + _TIFFfree(buf); + heif_image_release(*image); + *image = nullptr; + return {heif_error_Invalid_input, heif_suberror_Unspecified, "Failed to read TIFF scanline"}; + } + uint16_t* src = static_cast(buf); + uint8_t* dst = py + row * y_stride; + if (outSpp == samplesPerPixel) { + for (uint32_t x = 0; x < width * outSpp; x++) { + dst[x] = static_cast(src[x] >> 8); + } + } + else { + for (uint32_t x = 0; x < width; x++) { + for (uint16_t c = 0; c < outSpp; c++) { + dst[x * outSpp + c] = static_cast(src[x * samplesPerPixel + c] >> 8); + } + } + } + } + } + else { + for (uint32_t row = 0; row < height; row++) { + if (TIFFReadScanline(tif, buf, row, 0) < 0) { + _TIFFfree(buf); + heif_image_release(*image); + *image = nullptr; + return {heif_error_Invalid_input, heif_suberror_Unspecified, "Failed to read TIFF scanline"}; + } + uint16_t* src = static_cast(buf); + uint16_t* dst = reinterpret_cast(py + row * y_stride); + if (outSpp == samplesPerPixel) { + for (uint32_t x = 0; x < width * outSpp; x++) { + dst[x] = static_cast(src[x] >> bdShift); + } + } + else { + for (uint32_t x = 0; x < width; x++) { + for (uint16_t c = 0; c < outSpp; c++) { + dst[x * outSpp + c] = static_cast(src[x * samplesPerPixel + c] >> bdShift); + } + } + } + } + } + _TIFFfree(buf); + } + return heif_error_ok; +} + +heif_error readPixelInterleave(TIFF *tif, uint16_t samplesPerPixel, bool hasAlpha, uint16_t bps, int output_bit_depth, heif_image **image) +{ + if (samplesPerPixel == 1) { + return readMono(tif, bps, output_bit_depth, image); + } else { + return readPixelInterleaveRGB(tif, samplesPerPixel, hasAlpha, bps, output_bit_depth, image); + } +} + +heif_error readBandInterleaveRGB(TIFF *tif, uint16_t samplesPerPixel, bool hasAlpha, uint16_t bps, int output_bit_depth, heif_image **image) +{ + uint32_t width, height; + heif_error err = getImageWidthAndHeight(tif, width, height); + if (err.code != heif_error_Ok) { + return err; + } + + uint16_t outSpp = (samplesPerPixel == 4 && !hasAlpha) ? 3 : samplesPerPixel; + + if (bps <= 8) { + heif_chroma chroma = get_heif_chroma(outSpp, 8); + err = heif_image_create((int)width, (int)height, heif_colorspace_RGB, chroma, image); + if (err.code != heif_error_Ok) { + return err; + } + heif_channel channel = heif_channel_interleaved; + heif_image_add_plane(*image, channel, (int)width, (int)height, outSpp * 8); + + size_t y_stride; + uint8_t *py = heif_image_get_plane2(*image, channel, &y_stride); + + uint8_t *buf = static_cast(_TIFFmalloc(TIFFScanlineSize(tif))); + for (uint16_t i = 0; i < outSpp; i++) { + uint8_t *dest = py + i; + for (uint32_t row = 0; row < height; row++) { + if (TIFFReadScanline(tif, buf, row, i) < 0) { + _TIFFfree(buf); + heif_image_release(*image); + *image = nullptr; + return {heif_error_Invalid_input, heif_suberror_Unspecified, "Failed to read TIFF scanline"}; + } + for (uint32_t x = 0; x < width; x++, dest += outSpp) { + *dest = buf[x]; + } + dest += (y_stride - width * outSpp); + } + } + _TIFFfree(buf); + } + else { + heif_chroma chroma = get_heif_chroma(outSpp, output_bit_depth); + err = heif_image_create((int)width, (int)height, heif_colorspace_RGB, chroma, image); + if (err.code != heif_error_Ok) { + return err; + } + heif_channel channel = heif_channel_interleaved; + int planeBitDepth = (output_bit_depth <= 8) ? outSpp * 8 : output_bit_depth; + heif_image_add_plane(*image, channel, (int)width, (int)height, planeBitDepth); + + size_t y_stride; + uint8_t *py = heif_image_get_plane2(*image, channel, &y_stride); + int bdShift = 16 - output_bit_depth; + uint8_t *buf = static_cast(_TIFFmalloc(TIFFScanlineSize(tif))); + + if (output_bit_depth <= 8) { + for (uint16_t i = 0; i < outSpp; i++) { + for (uint32_t row = 0; row < height; row++) { + if (TIFFReadScanline(tif, buf, row, i) < 0) { + _TIFFfree(buf); + heif_image_release(*image); + *image = nullptr; + return {heif_error_Invalid_input, heif_suberror_Unspecified, "Failed to read TIFF scanline"}; + } + uint16_t* src = reinterpret_cast(buf); + uint8_t* dst = py + row * y_stride + i; + for (uint32_t x = 0; x < width; x++) { + dst[x * outSpp] = static_cast(src[x] >> 8); + } + } + } + } + else { + for (uint16_t i = 0; i < outSpp; i++) { + for (uint32_t row = 0; row < height; row++) { + if (TIFFReadScanline(tif, buf, row, i) < 0) { + _TIFFfree(buf); + heif_image_release(*image); + *image = nullptr; + return {heif_error_Invalid_input, heif_suberror_Unspecified, "Failed to read TIFF scanline"}; + } + uint16_t* src = reinterpret_cast(buf); + uint16_t* dst = reinterpret_cast(py + row * y_stride); + for (uint32_t x = 0; x < width; x++) { + dst[x * outSpp + i] = static_cast(src[x] >> bdShift); + } + } + } + } + _TIFFfree(buf); + } + return heif_error_ok; +} + + +heif_error readBandInterleave(TIFF *tif, uint16_t samplesPerPixel, bool hasAlpha, uint16_t bps, int output_bit_depth, heif_image **image) +{ + if (samplesPerPixel == 1) { + return readMono(tif, bps, output_bit_depth, image); + } else if (samplesPerPixel == 3) { + return readBandInterleaveRGB(tif, samplesPerPixel, hasAlpha, bps, output_bit_depth, image); + } else if (samplesPerPixel == 4) { + return readBandInterleaveRGB(tif, samplesPerPixel, hasAlpha, bps, output_bit_depth, image); + } else { + struct heif_error err = { + .code = heif_error_Unsupported_feature, + .subcode = heif_suberror_Unspecified, + .message = "Only 1, 3 and 4 bands are supported"}; + return err; + } +} + + +#if WITH_UNCOMPRESSED_CODEC +static heif_error readMonoFloat(TIFF* tif, heif_image** image) +{ + uint32_t width, height; + heif_error err = getImageWidthAndHeight(tif, width, height); + if (err.code != heif_error_Ok) { + return err; + } + + err = heif_image_create((int)width, (int)height, heif_colorspace_nonvisual, heif_chroma_undefined, image); + if (err.code != heif_error_Ok) { + return err; + } + + uint32_t component_idx; + err = heif_image_add_component(*image, (int)width, (int)height, + heif_uncompressed_component_type_monochrome, + heif_channel_datatype_floating_point, 32, &component_idx); + if (err.code != heif_error_Ok) { + heif_image_release(*image); + *image = nullptr; + return err; + } + + size_t stride; + float* plane = heif_image_get_component_float32(*image, component_idx, &stride); + if (!plane) { + heif_image_release(*image); + *image = nullptr; + return {heif_error_Memory_allocation_error, heif_suberror_Unspecified, "Failed to get float plane"}; + } + + tdata_t buf = _TIFFmalloc(TIFFScanlineSize(tif)); + for (uint32_t row = 0; row < height; row++) { + if (TIFFReadScanline(tif, buf, row, 0) < 0) { + _TIFFfree(buf); + heif_image_release(*image); + *image = nullptr; + return {heif_error_Invalid_input, heif_suberror_Unspecified, "Failed to read TIFF scanline"}; + } + memcpy(reinterpret_cast(plane) + row * stride, buf, width * sizeof(float)); + } + _TIFFfree(buf); + + return heif_error_ok; +} + + +static heif_error readMonoSignedInt(TIFF* tif, uint16_t bps, heif_image** image) +{ + uint32_t width, height; + heif_error err = getImageWidthAndHeight(tif, width, height); + if (err.code != heif_error_Ok) { + return err; + } + + err = heif_image_create((int)width, (int)height, heif_colorspace_nonvisual, heif_chroma_undefined, image); + if (err.code != heif_error_Ok) { + return err; + } + + uint32_t component_idx; + err = heif_image_add_component(*image, (int)width, (int)height, + heif_uncompressed_component_type_monochrome, + heif_channel_datatype_signed_integer, bps, &component_idx); + if (err.code != heif_error_Ok) { + heif_image_release(*image); + *image = nullptr; + return err; + } + + size_t stride; + uint8_t* plane; + if (bps == 8) { + plane = reinterpret_cast(heif_image_get_component_int8(*image, component_idx, &stride)); + } + else { + plane = reinterpret_cast(heif_image_get_component_int16(*image, component_idx, &stride)); + } + if (!plane) { + heif_image_release(*image); + *image = nullptr; + return {heif_error_Memory_allocation_error, heif_suberror_Unspecified, "Failed to get signed int plane"}; + } + + int bytesPerSample = bps / 8; + tdata_t buf = _TIFFmalloc(TIFFScanlineSize(tif)); + for (uint32_t row = 0; row < height; row++) { + if (TIFFReadScanline(tif, buf, row, 0) < 0) { + _TIFFfree(buf); + heif_image_release(*image); + *image = nullptr; + return {heif_error_Invalid_input, heif_suberror_Unspecified, "Failed to read TIFF scanline"}; + } + memcpy(plane + row * stride, buf, width * bytesPerSample); + } + _TIFFfree(buf); + + return heif_error_ok; +} +#endif + + +static void suppress_warnings(const char* module, const char* fmt, va_list ap) { + // Do nothing +} + + +static heif_error validateTiffFormat(TIFF* tif, uint16_t& samplesPerPixel, uint16_t& bps, + uint16_t& config, bool& hasAlpha, uint16_t& sampleFormat) +{ + uint16_t shortv; + if (TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &shortv) && shortv == PHOTOMETRIC_PALETTE) { + return {heif_error_Unsupported_feature, heif_suberror_Unspecified, + "Palette TIFF images are not supported yet"}; + } + + TIFFGetField(tif, TIFFTAG_PLANARCONFIG, &config); + TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &samplesPerPixel); + if (samplesPerPixel != 1 && samplesPerPixel != 3 && samplesPerPixel != 4) { + return {heif_error_Invalid_input, heif_suberror_Unspecified, + "Only 1, 3 and 4 samples per pixel are supported."}; + } + + // Determine whether the 4th sample is true alpha or an unrelated extra sample + hasAlpha = false; + if (samplesPerPixel == 4) { + uint16_t extraCount = 0; + uint16_t* extraTypes = nullptr; + if (TIFFGetField(tif, TIFFTAG_EXTRASAMPLES, &extraCount, &extraTypes) && extraCount > 0) { + hasAlpha = (extraTypes[0] == EXTRASAMPLE_ASSOCALPHA || extraTypes[0] == EXTRASAMPLE_UNASSALPHA); + } + else { + // No EXTRASAMPLES tag with 4 spp — assume the extra sample is not alpha + hasAlpha = false; + } + } + + TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bps); + + if (!TIFFGetField(tif, TIFFTAG_SAMPLEFORMAT, &sampleFormat)) { + sampleFormat = SAMPLEFORMAT_UINT; + } + + if (sampleFormat == SAMPLEFORMAT_IEEEFP) { + if (bps != 32) { + return {heif_error_Unsupported_feature, heif_suberror_Unspecified, + "Only 32-bit floating point TIFF is supported."}; + } + if (samplesPerPixel != 1) { + return {heif_error_Unsupported_feature, heif_suberror_Unspecified, + "Only monochrome floating point TIFF is supported."}; + } + } + else if (sampleFormat == SAMPLEFORMAT_INT) { + if (bps != 8 && bps != 16) { + return {heif_error_Invalid_input, heif_suberror_Unspecified, + "Only 8 and 16 bits per sample are supported for signed integer TIFF."}; + } + if (samplesPerPixel != 1) { + return {heif_error_Unsupported_feature, heif_suberror_Unspecified, + "Only monochrome signed integer TIFF is supported."}; + } + } + else if (sampleFormat == SAMPLEFORMAT_UINT) { + if (bps != 8 && bps != 16) { + return {heif_error_Invalid_input, heif_suberror_Unspecified, + "Only 8 and 16 bits per sample are supported."}; + } + } + else { + return {heif_error_Unsupported_feature, heif_suberror_Unspecified, + "Unsupported TIFF sample format."}; + } + + return heif_error_ok; +} + + +static YCbCrInfo getYCbCrInfo(TIFF* tif) +{ + YCbCrInfo info; + uint16_t photometric; + if (TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &photometric) && photometric == PHOTOMETRIC_YCBCR) { + info.is_ycbcr = true; + TIFFGetFieldDefaulted(tif, TIFFTAG_YCBCRSUBSAMPLING, &info.horiz_sub, &info.vert_sub); + } + return info; +} + + +static heif_chroma ycbcrChroma(const YCbCrInfo& ycbcr) +{ + if (ycbcr.horiz_sub == 2 && ycbcr.vert_sub == 2) return heif_chroma_420; + if (ycbcr.horiz_sub == 2 && ycbcr.vert_sub == 1) return heif_chroma_422; + return heif_chroma_444; +} + + +// Deinterleave libtiff's packed YCbCr format into separate Y/Cb/Cr planes. +// Packed format: MCUs of (horiz_sub * vert_sub) Y samples + 1 Cb + 1 Cr. +// block_w/block_h must be multiples of horiz_sub/vert_sub respectively. +// actual_w/actual_h are the clipped dimensions for edge tiles/strips. +static void deinterleaveYCbCr( + const uint8_t* src, + uint32_t block_w, uint32_t block_h, + uint32_t actual_w, uint32_t actual_h, + uint16_t horiz_sub, uint16_t vert_sub, + uint8_t* y_plane, size_t y_stride, + uint8_t* cb_plane, size_t cb_stride, + uint8_t* cr_plane, size_t cr_stride) +{ + uint32_t mcus_h = block_w / horiz_sub; + uint32_t mcu_rows = block_h / vert_sub; + uint32_t mcu_size = horiz_sub * vert_sub + 2; + + uint32_t chroma_w = (actual_w + horiz_sub - 1) / horiz_sub; + uint32_t chroma_h = (actual_h + vert_sub - 1) / vert_sub; + + for (uint32_t mcu_y = 0; mcu_y < mcu_rows; mcu_y++) { + for (uint32_t mcu_x = 0; mcu_x < mcus_h; mcu_x++) { + const uint8_t* mcu = src + (mcu_y * mcus_h + mcu_x) * mcu_size; + + // Scatter Y samples + for (uint32_t vy = 0; vy < vert_sub; vy++) { + uint32_t py = mcu_y * vert_sub + vy; + if (py >= actual_h) break; + for (uint32_t hx = 0; hx < horiz_sub; hx++) { + uint32_t px = mcu_x * horiz_sub + hx; + if (px >= actual_w) break; + y_plane[py * y_stride + px] = mcu[vy * horiz_sub + hx]; + } + } + + // Scatter Cb/Cr + if (mcu_x < chroma_w && mcu_y < chroma_h) { + cb_plane[mcu_y * cb_stride + mcu_x] = mcu[horiz_sub * vert_sub]; + cr_plane[mcu_y * cr_stride + mcu_x] = mcu[horiz_sub * vert_sub + 1]; + } + } + } +} + + +// Create a YCbCr heif_image from a single packed YCbCr tile/block. +static heif_error readYCbCrBlock( + const uint8_t* tile_buf, + uint32_t block_w, uint32_t block_h, + uint32_t actual_w, uint32_t actual_h, + const YCbCrInfo& ycbcr, + heif_image** out_image) +{ + heif_chroma chroma = ycbcrChroma(ycbcr); + + heif_error err = heif_image_create((int)actual_w, (int)actual_h, heif_colorspace_YCbCr, chroma, out_image); + if (err.code != heif_error_Ok) return err; + + heif_image_add_plane(*out_image, heif_channel_Y, (int)actual_w, (int)actual_h, 8); + uint32_t chroma_w = (actual_w + ycbcr.horiz_sub - 1) / ycbcr.horiz_sub; + uint32_t chroma_h = (actual_h + ycbcr.vert_sub - 1) / ycbcr.vert_sub; + heif_image_add_plane(*out_image, heif_channel_Cb, (int)chroma_w, (int)chroma_h, 8); + heif_image_add_plane(*out_image, heif_channel_Cr, (int)chroma_w, (int)chroma_h, 8); + + size_t y_stride, cb_stride, cr_stride; + uint8_t* y_plane = heif_image_get_plane2(*out_image, heif_channel_Y, &y_stride); + uint8_t* cb_plane = heif_image_get_plane2(*out_image, heif_channel_Cb, &cb_stride); + uint8_t* cr_plane = heif_image_get_plane2(*out_image, heif_channel_Cr, &cr_stride); + + deinterleaveYCbCr(tile_buf, block_w, block_h, actual_w, actual_h, + ycbcr.horiz_sub, ycbcr.vert_sub, + y_plane, y_stride, cb_plane, cb_stride, cr_plane, cr_stride); + + return heif_error_ok; +} + + +static heif_error readTiledContiguous(TIFF* tif, uint32_t width, uint32_t height, + uint32_t tile_width, uint32_t tile_height, + uint16_t samplesPerPixel, bool hasAlpha, + uint16_t bps, int output_bit_depth, + uint16_t sampleFormat, heif_image** out_image) +{ + // --- YCbCr path: deinterleave packed MCU data into planar Y/Cb/Cr --- + YCbCrInfo ycbcr = getYCbCrInfo(tif); + if (ycbcr.is_ycbcr) { + if (bps != 8) { + return {heif_error_Unsupported_feature, heif_suberror_Unspecified, + "Only 8-bit YCbCr TIFF is supported."}; + } + + if (width > std::numeric_limits::max() || height > std::numeric_limits::max()) { + return {heif_error_Invalid_input, heif_suberror_Unspecified, "TIFF image size exceeds maximum supported by libheif."}; + } + + + heif_chroma chroma = ycbcrChroma(ycbcr); + heif_error err = heif_image_create((int)width, (int)height, heif_colorspace_YCbCr, chroma, out_image); + if (err.code != heif_error_Ok) return err; + + heif_image_add_plane(*out_image, heif_channel_Y, (int)width, (int)height, 8); + uint32_t chroma_w = (width + ycbcr.horiz_sub - 1) / ycbcr.horiz_sub; + uint32_t chroma_h = (height + ycbcr.vert_sub - 1) / ycbcr.vert_sub; + heif_image_add_plane(*out_image, heif_channel_Cb, (int)chroma_w, (int)chroma_h, 8); + heif_image_add_plane(*out_image, heif_channel_Cr, (int)chroma_w, (int)chroma_h, 8); + + size_t y_stride, cb_stride, cr_stride; + uint8_t* y_plane = heif_image_get_plane2(*out_image, heif_channel_Y, &y_stride); + uint8_t* cb_plane = heif_image_get_plane2(*out_image, heif_channel_Cb, &cb_stride); + uint8_t* cr_plane = heif_image_get_plane2(*out_image, heif_channel_Cr, &cr_stride); + + tmsize_t tile_buf_size = TIFFTileSize(tif); + std::vector tile_buf(tile_buf_size); + + uint32_t n_cols = (width - 1) / tile_width + 1; + uint32_t n_rows = (height - 1) / tile_height + 1; + + for (uint32_t ty = 0; ty < n_rows; ty++) { + for (uint32_t tx = 0; tx < n_cols; tx++) { + tmsize_t read = TIFFReadEncodedTile(tif, TIFFComputeTile(tif, tx * tile_width, ty * tile_height, 0, 0), + tile_buf.data(), tile_buf_size); + if (read < 0) { + heif_image_release(*out_image); + *out_image = nullptr; + return {heif_error_Invalid_input, heif_suberror_Unspecified, "Failed to read TIFF tile"}; + } + + uint32_t actual_w = std::min(tile_width, width - tx * tile_width); + uint32_t actual_h = std::min(tile_height, height - ty * tile_height); + + uint32_t y_offset = ty * tile_height; + uint32_t x_offset = tx * tile_width; + uint32_t chroma_x_offset = x_offset / ycbcr.horiz_sub; + uint32_t chroma_y_offset = y_offset / ycbcr.vert_sub; + + deinterleaveYCbCr(tile_buf.data(), tile_width, tile_height, actual_w, actual_h, + ycbcr.horiz_sub, ycbcr.vert_sub, + y_plane + y_offset * y_stride + x_offset, y_stride, + cb_plane + chroma_y_offset * cb_stride + chroma_x_offset, cb_stride, + cr_plane + chroma_y_offset * cr_stride + chroma_x_offset, cr_stride); + } + } + + return heif_error_ok; + } + + bool isFloat = (sampleFormat == SAMPLEFORMAT_IEEEFP); + + if (isFloat) { +#if WITH_UNCOMPRESSED_CODEC + heif_error err = heif_image_create((int)width, (int)height, heif_colorspace_nonvisual, heif_chroma_undefined, out_image); + if (err.code != heif_error_Ok) return err; + + uint32_t component_idx; + err = heif_image_add_component(*out_image, (int)width, (int)height, + heif_uncompressed_component_type_monochrome, + heif_channel_datatype_floating_point, 32, &component_idx); + if (err.code != heif_error_Ok) { + heif_image_release(*out_image); + *out_image = nullptr; + return err; + } + + size_t out_stride; + float* out_plane = heif_image_get_component_float32(*out_image, component_idx, &out_stride); + if (!out_plane) { + heif_image_release(*out_image); + *out_image = nullptr; + return {heif_error_Memory_allocation_error, heif_suberror_Unspecified, "Failed to get float plane"}; + } + + tmsize_t tile_buf_size = TIFFTileSize(tif); + std::vector tile_buf(tile_buf_size); + + uint32_t n_cols = (width - 1) / tile_width + 1; + uint32_t n_rows = (height - 1) / tile_height + 1; + + for (uint32_t ty = 0; ty < n_rows; ty++) { + for (uint32_t tx = 0; tx < n_cols; tx++) { + tmsize_t read = TIFFReadEncodedTile(tif, TIFFComputeTile(tif, tx * tile_width, ty * tile_height, 0, 0), + tile_buf.data(), tile_buf_size); + if (read < 0) { + heif_image_release(*out_image); + *out_image = nullptr; + return {heif_error_Invalid_input, heif_suberror_Unspecified, "Failed to read TIFF tile"}; + } + + uint32_t actual_w = std::min(tile_width, width - tx * tile_width); + uint32_t actual_h = std::min(tile_height, height - ty * tile_height); + + for (uint32_t row = 0; row < actual_h; row++) { + uint8_t* dst = reinterpret_cast(out_plane) + (size_t)(ty * tile_height + row) * out_stride + + (size_t)tx * tile_width * sizeof(float); + float* src = reinterpret_cast(tile_buf.data() + (size_t)row * tile_width * sizeof(float)); + memcpy(dst, src, actual_w * sizeof(float)); + } + } + } + + return heif_error_ok; +#else + return {heif_error_Unsupported_feature, heif_suberror_Unspecified, + "Floating point TIFF requires uncompressed codec support (WITH_UNCOMPRESSED_CODEC)."}; +#endif + } + + bool isSignedInt = (sampleFormat == SAMPLEFORMAT_INT); + + if (isSignedInt) { +#if WITH_UNCOMPRESSED_CODEC + heif_error err = heif_image_create((int)width, (int)height, heif_colorspace_nonvisual, heif_chroma_undefined, out_image); + if (err.code != heif_error_Ok) return err; + + uint32_t component_idx; + err = heif_image_add_component(*out_image, (int)width, (int)height, + heif_uncompressed_component_type_monochrome, + heif_channel_datatype_signed_integer, bps, &component_idx); + if (err.code != heif_error_Ok) { + heif_image_release(*out_image); + *out_image = nullptr; + return err; + } + + size_t out_stride; + uint8_t* out_plane; + if (bps == 8) { + out_plane = reinterpret_cast(heif_image_get_component_int8(*out_image, component_idx, &out_stride)); + } + else { + out_plane = reinterpret_cast(heif_image_get_component_int16(*out_image, component_idx, &out_stride)); + } + if (!out_plane) { + heif_image_release(*out_image); + *out_image = nullptr; + return {heif_error_Memory_allocation_error, heif_suberror_Unspecified, "Failed to get signed int plane"}; + } + + int bytesPerSample = bps / 8; + tmsize_t tile_buf_size = TIFFTileSize(tif); + std::vector tile_buf(tile_buf_size); + + uint32_t n_cols = (width - 1) / tile_width + 1; + uint32_t n_rows = (height - 1) / tile_height + 1; + + for (uint32_t ty = 0; ty < n_rows; ty++) { + for (uint32_t tx = 0; tx < n_cols; tx++) { + tmsize_t read = TIFFReadEncodedTile(tif, TIFFComputeTile(tif, tx * tile_width, ty * tile_height, 0, 0), + tile_buf.data(), tile_buf_size); + if (read < 0) { + heif_image_release(*out_image); + *out_image = nullptr; + return {heif_error_Invalid_input, heif_suberror_Unspecified, "Failed to read TIFF tile"}; + } + + uint32_t actual_w = std::min(tile_width, width - tx * tile_width); + uint32_t actual_h = std::min(tile_height, height - ty * tile_height); + + for (uint32_t row = 0; row < actual_h; row++) { + uint8_t* dst = out_plane + ((size_t)ty * tile_height + row) * out_stride + + (size_t)tx * tile_width * bytesPerSample; + uint8_t* src = tile_buf.data() + (size_t)row * tile_width * bytesPerSample; + memcpy(dst, src, actual_w * bytesPerSample); + } + } + } + + return heif_error_ok; +#else + return {heif_error_Unsupported_feature, heif_suberror_Unspecified, + "Signed integer TIFF requires uncompressed codec support (WITH_UNCOMPRESSED_CODEC)."}; +#endif + } + + uint16_t outSpp = (samplesPerPixel == 4 && !hasAlpha) ? 3 : samplesPerPixel; + int effectiveBitDepth = (bps <= 8) ? 8 : output_bit_depth; + heif_chroma chroma = get_heif_chroma(outSpp, effectiveBitDepth); + heif_colorspace colorspace = (outSpp == 1) ? heif_colorspace_monochrome : heif_colorspace_RGB; + heif_channel channel = (outSpp == 1) ? heif_channel_Y : heif_channel_interleaved; + + heif_error err = heif_image_create((int)width, (int)height, colorspace, chroma, out_image); + if (err.code != heif_error_Ok) return err; + + int planeBitDepth = (effectiveBitDepth <= 8) ? outSpp * 8 : effectiveBitDepth; + heif_image_add_plane(*out_image, channel, (int)width, (int)height, planeBitDepth); + + size_t out_stride; + uint8_t* out_plane = heif_image_get_plane2(*out_image, channel, &out_stride); + + tmsize_t tile_buf_size = TIFFTileSize(tif); + std::vector tile_buf(tile_buf_size); + + uint32_t n_cols = (width - 1) / tile_width + 1; + uint32_t n_rows = (height - 1) / tile_height + 1; + + for (uint32_t ty = 0; ty < n_rows; ty++) { + for (uint32_t tx = 0; tx < n_cols; tx++) { + tmsize_t read = TIFFReadEncodedTile(tif, TIFFComputeTile(tif, tx * tile_width, ty * tile_height, 0, 0), + tile_buf.data(), tile_buf_size); + if (read < 0) { + heif_image_release(*out_image); + *out_image = nullptr; + return {heif_error_Invalid_input, heif_suberror_Unspecified, "Failed to read TIFF tile"}; + } + + uint32_t actual_w = std::min(tile_width, width - tx * tile_width); + uint32_t actual_h = std::min(tile_height, height - ty * tile_height); + + if (bps <= 8) { + for (uint32_t row = 0; row < actual_h; row++) { + uint8_t* dst = out_plane + ((size_t)ty * tile_height + row) * out_stride + (size_t)tx * tile_width * outSpp; + uint8_t* src = tile_buf.data() + (size_t)row * tile_width * samplesPerPixel; + if (outSpp == samplesPerPixel) { + memcpy(dst, src, actual_w * outSpp); + } + else { + for (uint32_t x = 0; x < actual_w; x++) { + memcpy(dst + x * outSpp, src + x * samplesPerPixel, outSpp); + } + } + } + } + else if (output_bit_depth <= 8) { + for (uint32_t row = 0; row < actual_h; row++) { + uint8_t* dst = out_plane + ((size_t)ty * tile_height + row) * out_stride + (size_t)tx * tile_width * outSpp; + uint16_t* src = reinterpret_cast(tile_buf.data() + (size_t)row * tile_width * samplesPerPixel * 2); + if (outSpp == samplesPerPixel) { + for (uint32_t x = 0; x < actual_w * outSpp; x++) { + dst[x] = static_cast(src[x] >> 8); + } + } + else { + for (uint32_t x = 0; x < actual_w; x++) { + for (uint16_t c = 0; c < outSpp; c++) { + dst[x * outSpp + c] = static_cast(src[x * samplesPerPixel + c] >> 8); + } + } + } + } + } + else { + int bdShift = 16 - output_bit_depth; + for (uint32_t row = 0; row < actual_h; row++) { + uint16_t* dst = reinterpret_cast(out_plane + ((size_t)ty * tile_height + row) * out_stride + + (size_t)tx * tile_width * outSpp * 2); + uint16_t* src = reinterpret_cast(tile_buf.data() + (size_t)row * tile_width * samplesPerPixel * 2); + if (outSpp == samplesPerPixel) { + if (bdShift == 0) { + memcpy(dst, src, actual_w * outSpp * 2); + } + else { + for (uint32_t x = 0; x < actual_w * outSpp; x++) { + dst[x] = static_cast(src[x] >> bdShift); + } + } + } + else { + for (uint32_t x = 0; x < actual_w; x++) { + for (uint16_t c = 0; c < outSpp; c++) { + dst[x * outSpp + c] = static_cast(src[x * samplesPerPixel + c] >> bdShift); + } + } + } + } + } + } + } + + return heif_error_ok; +} + + +static heif_error readTiledSeparate(TIFF* tif, uint32_t width, uint32_t height, + uint32_t tile_width, uint32_t tile_height, + uint16_t samplesPerPixel, bool hasAlpha, + uint16_t bps, int output_bit_depth, + uint16_t sampleFormat, heif_image** out_image) +{ + // For mono float/signed int, separate layout is the same as contiguous (1 sample) + if (sampleFormat == SAMPLEFORMAT_IEEEFP || sampleFormat == SAMPLEFORMAT_INT) { +#if WITH_UNCOMPRESSED_CODEC + return readTiledContiguous(tif, width, height, tile_width, tile_height, + samplesPerPixel, hasAlpha, bps, output_bit_depth, + sampleFormat, out_image); +#else + return {heif_error_Unsupported_feature, heif_suberror_Unspecified, + "Float/signed integer TIFF requires uncompressed codec support (WITH_UNCOMPRESSED_CODEC)."}; +#endif + } + + uint16_t outSpp = (samplesPerPixel == 4 && !hasAlpha) ? 3 : samplesPerPixel; + int effectiveBitDepth = (bps <= 8) ? 8 : output_bit_depth; + heif_chroma chroma = get_heif_chroma(outSpp, effectiveBitDepth); + heif_colorspace colorspace = (outSpp == 1) ? heif_colorspace_monochrome : heif_colorspace_RGB; + heif_channel channel = (outSpp == 1) ? heif_channel_Y : heif_channel_interleaved; + + heif_error err = heif_image_create((int)width, (int)height, colorspace, chroma, out_image); + if (err.code != heif_error_Ok) return err; + + int planeBitDepth = (effectiveBitDepth <= 8) ? outSpp * 8 : effectiveBitDepth; + heif_image_add_plane(*out_image, channel, (int)width, (int)height, planeBitDepth); + + size_t out_stride; + uint8_t* out_plane = heif_image_get_plane2(*out_image, channel, &out_stride); + + tmsize_t tile_buf_size = TIFFTileSize(tif); + std::vector tile_buf(tile_buf_size); + + uint32_t n_cols = (width - 1) / tile_width + 1; + uint32_t n_rows = (height - 1) / tile_height + 1; + + for (uint16_t s = 0; s < outSpp; s++) { + for (uint32_t ty = 0; ty < n_rows; ty++) { + for (uint32_t tx = 0; tx < n_cols; tx++) { + tmsize_t read = TIFFReadEncodedTile(tif, TIFFComputeTile(tif, tx * tile_width, ty * tile_height, 0, s), + tile_buf.data(), tile_buf_size); + if (read < 0) { + heif_image_release(*out_image); + *out_image = nullptr; + return {heif_error_Invalid_input, heif_suberror_Unspecified, "Failed to read TIFF tile"}; + } + + uint32_t actual_w = std::min(tile_width, width - tx * tile_width); + uint32_t actual_h = std::min(tile_height, height - ty * tile_height); + + if (bps <= 8) { + for (uint32_t row = 0; row < actual_h; row++) { + uint8_t* dst = out_plane + ((size_t)ty * tile_height + row) * out_stride + (size_t)tx * tile_width * outSpp + s; + uint8_t* src = tile_buf.data() + (size_t)row * tile_width; + for (uint32_t x = 0; x < actual_w; x++) { + dst[x * outSpp] = src[x]; + } + } + } + else if (output_bit_depth <= 8) { + for (uint32_t row = 0; row < actual_h; row++) { + uint8_t* dst = out_plane + ((size_t)ty * tile_height + row) * out_stride + (size_t)tx * tile_width * outSpp + s; + uint16_t* src = reinterpret_cast(tile_buf.data() + (size_t)row * tile_width * 2); + for (uint32_t x = 0; x < actual_w; x++) { + dst[x * outSpp] = static_cast(src[x] >> 8); + } + } + } + else { + int bdShift = 16 - output_bit_depth; + for (uint32_t row = 0; row < actual_h; row++) { + uint16_t* dst = reinterpret_cast(out_plane + ((size_t)ty * tile_height + row) * out_stride) + (size_t)tx * tile_width * outSpp + s; + uint16_t* src = reinterpret_cast(tile_buf.data() + (size_t)row * tile_width * 2); + for (uint32_t x = 0; x < actual_w; x++) { + dst[x * outSpp] = static_cast(src[x] >> bdShift); + } + } + } + } + } + } + + return heif_error_ok; +} + + +heif_error loadTIFF(const char* filename, int output_bit_depth, InputImage *input_image) { + TIFFSetWarningHandler(suppress_warnings); + + std::unique_ptr tifPtr(TIFFOpen(filename, "r"), [](TIFF* tif) { TIFFClose(tif); }); + if (!tifPtr) { + return {heif_error_Invalid_input, heif_suberror_Unspecified, "Cannot open TIFF file"}; + } + + TIFF* tif = tifPtr.get(); + + uint16_t samplesPerPixel, bps, config, sampleFormat; + bool hasAlpha; + heif_error err = validateTiffFormat(tif, samplesPerPixel, bps, config, hasAlpha, sampleFormat); + if (err.code != heif_error_Ok) return err; + + // For PLANARCONFIG_SEPARATE + YCbCr, tell libtiff to convert to RGB on the fly + YCbCrInfo ycbcr = getYCbCrInfo(tif); + if (ycbcr.is_ycbcr && config == PLANARCONFIG_SEPARATE) { + TIFFSetField(tif, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB); + } + + bool isFloat = (sampleFormat == SAMPLEFORMAT_IEEEFP); + bool isSignedInt = (sampleFormat == SAMPLEFORMAT_INT); + + // For 8-bit source, always produce 8-bit output (ignore output_bit_depth). + // For float, use 32-bit. For signed int, preserve original bit depth. + int effectiveOutputBitDepth = isFloat ? 32 : (isSignedInt ? bps : ((bps <= 8) ? 8 : output_bit_depth)); + + struct heif_image* image = nullptr; + + if (TIFFIsTiled(tif)) { + uint32_t width, height, tile_width, tile_height; + err = getImageWidthAndHeight(tif, width, height); + if (err.code != heif_error_Ok) return err; + + if (!TIFFGetField(tif, TIFFTAG_TILEWIDTH, &tile_width) || + !TIFFGetField(tif, TIFFTAG_TILELENGTH, &tile_height)) { + return {heif_error_Invalid_input, heif_suberror_Unspecified, "Cannot read TIFF tile dimensions"}; + } + + if (tile_width == 0 || tile_height == 0) { + return {heif_error_Invalid_input, heif_suberror_Unspecified, "Invalid TIFF tile dimensions"}; + } + + switch (config) { + case PLANARCONFIG_CONTIG: + err = readTiledContiguous(tif, width, height, tile_width, tile_height, samplesPerPixel, hasAlpha, bps, effectiveOutputBitDepth, sampleFormat, &image); + break; + case PLANARCONFIG_SEPARATE: + err = readTiledSeparate(tif, width, height, tile_width, tile_height, samplesPerPixel, hasAlpha, bps, effectiveOutputBitDepth, sampleFormat, &image); + break; + default: + return {heif_error_Invalid_input, heif_suberror_Unspecified, "Unsupported planar configuration"}; + } + } + else { + if (isFloat) { +#if WITH_UNCOMPRESSED_CODEC + err = readMonoFloat(tif, &image); +#else + return {heif_error_Unsupported_feature, heif_suberror_Unspecified, + "Floating point TIFF requires uncompressed codec support (WITH_UNCOMPRESSED_CODEC)."}; +#endif + } + else if (isSignedInt) { +#if WITH_UNCOMPRESSED_CODEC + err = readMonoSignedInt(tif, bps, &image); +#else + return {heif_error_Unsupported_feature, heif_suberror_Unspecified, + "Signed integer TIFF requires uncompressed codec support (WITH_UNCOMPRESSED_CODEC)."}; +#endif + } + else { + switch (config) { + case PLANARCONFIG_CONTIG: + err = readPixelInterleave(tif, samplesPerPixel, hasAlpha, bps, effectiveOutputBitDepth, &image); + break; + case PLANARCONFIG_SEPARATE: + err = readBandInterleave(tif, samplesPerPixel, hasAlpha, bps, effectiveOutputBitDepth, &image); + break; + default: + return {heif_error_Invalid_input, heif_suberror_Unspecified, "Unsupported planar configuration"}; + } + } + } + + if (err.code != heif_error_Ok) { + return err; + } + + input_image->image = std::shared_ptr(image, + [](heif_image* img) { heif_image_release(img); }); + + // Unfortunately libtiff doesn't provide a way to read a raw dictionary. + // Therefore we manually parse the EXIF data, extract the tags and encode + // them for use in the HEIF image. + std::unique_ptr tags = ExifTags::Parse(tif); + if (tags) { + tags->Encode(&(input_image->exif)); + } + return heif_error_ok; +} + + +// --- TiledTiffReader --- + +void TiledTiffReader::TiffCloser::operator()(void* tif) const { + if (tif) { + TIFFClose(static_cast(tif)); + } +} + + +std::unique_ptr TiledTiffReader::open(const char* filename, heif_error* out_err) +{ + TIFFSetWarningHandler(suppress_warnings); + + TIFF* tif = TIFFOpen(filename, "r"); + if (!tif) { + *out_err = {heif_error_Invalid_input, heif_suberror_Unspecified, "Cannot open TIFF file"}; + return nullptr; + } + + if (!TIFFIsTiled(tif)) { + TIFFClose(tif); + *out_err = heif_error_ok; + return nullptr; + } + + auto reader = std::unique_ptr(new TiledTiffReader()); + reader->m_tif.reset(tif); + + uint16_t bps; + heif_error err = validateTiffFormat(tif, reader->m_samples_per_pixel, bps, reader->m_planar_config, reader->m_has_alpha, reader->m_sample_format); + if (err.code != heif_error_Ok) { + *out_err = err; + return nullptr; + } + reader->m_bits_per_sample = bps; + + reader->m_ycbcr = getYCbCrInfo(tif); + if (reader->m_ycbcr.is_ycbcr && reader->m_planar_config == PLANARCONFIG_SEPARATE) { + TIFFSetField(tif, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB); + reader->m_ycbcr.is_ycbcr = false; // data comes out as RGB now + } + + if (!TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &reader->m_image_width) || + !TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &reader->m_image_height)) { + *out_err = {heif_error_Invalid_input, heif_suberror_Unspecified, "Cannot read TIFF image dimensions"}; + return nullptr; + } + + if (!TIFFGetField(tif, TIFFTAG_TILEWIDTH, &reader->m_tile_width) || + !TIFFGetField(tif, TIFFTAG_TILELENGTH, &reader->m_tile_height)) { + *out_err = {heif_error_Invalid_input, heif_suberror_Unspecified, "Cannot read TIFF tile dimensions"}; + return nullptr; + } + + if (reader->m_tile_width == 0 || reader->m_tile_height == 0) { + *out_err = {heif_error_Invalid_input, heif_suberror_Unspecified, "Invalid TIFF tile dimensions"}; + return nullptr; + } + + reader->m_n_columns = (reader->m_image_width - 1) / reader->m_tile_width + 1; + reader->m_n_rows = (reader->m_image_height - 1) / reader->m_tile_height + 1; + + // Detect overview directories (reduced-resolution images) + tdir_t n_dirs = TIFFNumberOfDirectories(tif); + for (uint16_t d = 1; d < n_dirs; d++) { + if (!TIFFSetDirectory(tif, d)) { + continue; + } + + uint32_t subfiletype = 0; + TIFFGetField(tif, TIFFTAG_SUBFILETYPE, &subfiletype); + if (!(subfiletype & FILETYPE_REDUCEDIMAGE)) { + continue; + } + + if (!TIFFIsTiled(tif)) { + continue; + } + + uint16_t spp, bps_ov, config_ov, sampleFormat_ov; + bool hasAlpha_ov; + heif_error valErr = validateTiffFormat(tif, spp, bps_ov, config_ov, hasAlpha_ov, sampleFormat_ov); + if (valErr.code != heif_error_Ok) { + continue; + } + + uint32_t ov_width, ov_height, ov_tw, ov_th; + if (!TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &ov_width) || + !TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &ov_height)) { + continue; + } + if (!TIFFGetField(tif, TIFFTAG_TILEWIDTH, &ov_tw) || + !TIFFGetField(tif, TIFFTAG_TILELENGTH, &ov_th)) { + continue; + } + + if (ov_width == 0 || ov_height == 0) { + continue; + } + + if (ov_tw == 0 || ov_th == 0) { + continue; + } + + reader->m_overviews.push_back({d, ov_width, ov_height, ov_tw, ov_th}); + } + + // Switch back to directory 0 + TIFFSetDirectory(tif, 0); + + *out_err = heif_error_ok; + return reader; +} + + +TiledTiffReader::~TiledTiffReader() = default; + + +bool TiledTiffReader::setDirectory(uint32_t dir_index) +{ + TIFF* tif = static_cast(m_tif.get()); + + if (!TIFFSetDirectory(tif, static_cast(dir_index))) { + return false; + } + + if (!TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &m_image_width) || + !TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &m_image_height)) { + return false; + } + + if (!TIFFGetField(tif, TIFFTAG_TILEWIDTH, &m_tile_width) || + !TIFFGetField(tif, TIFFTAG_TILELENGTH, &m_tile_height)) { + return false; + } + + if (m_image_width == 0 || m_image_height == 0) { + return false; + } + + if (m_tile_width == 0 || m_tile_height == 0) { + return false; + } + + uint16_t bps; + heif_error err = validateTiffFormat(tif, m_samples_per_pixel, bps, m_planar_config, m_has_alpha, m_sample_format); + if (err.code != heif_error_Ok) { + return false; + } + m_bits_per_sample = bps; + + m_ycbcr = getYCbCrInfo(tif); + if (m_ycbcr.is_ycbcr && m_planar_config == PLANARCONFIG_SEPARATE) { + TIFFSetField(tif, TIFFTAG_JPEGCOLORMODE, JPEGCOLORMODE_RGB); + m_ycbcr.is_ycbcr = false; + } + + m_n_columns = (m_image_width - 1) / m_tile_width + 1; + m_n_rows = (m_image_height - 1) / m_tile_height + 1; + + return true; +} + + +heif_error TiledTiffReader::readTile(uint32_t tx, uint32_t ty, int output_bit_depth, heif_image** out_image) +{ + TIFF* tif = static_cast(m_tif.get()); + + uint32_t actual_w = std::min(m_tile_width, m_image_width - tx * m_tile_width); + uint32_t actual_h = std::min(m_tile_height, m_image_height - ty * m_tile_height); + + // --- YCbCr path (PLANARCONFIG_CONTIG only; SEPARATE was converted to RGB in open/setDirectory) --- + if (m_ycbcr.is_ycbcr && m_planar_config == PLANARCONFIG_CONTIG) { + if (m_bits_per_sample != 8) { + return {heif_error_Unsupported_feature, heif_suberror_Unspecified, + "Only 8-bit YCbCr TIFF is supported."}; + } + + tmsize_t tile_buf_size = TIFFTileSize(tif); + std::vector tile_buf(tile_buf_size); + + tmsize_t read = TIFFReadEncodedTile(tif, TIFFComputeTile(tif, tx * m_tile_width, ty * m_tile_height, 0, 0), + tile_buf.data(), tile_buf_size); + if (read < 0) { + return {heif_error_Invalid_input, heif_suberror_Unspecified, "Failed to read TIFF tile"}; + } + + return readYCbCrBlock(tile_buf.data(), m_tile_width, m_tile_height, actual_w, actual_h, m_ycbcr, out_image); + } + + if (m_sample_format == SAMPLEFORMAT_IEEEFP) { +#if WITH_UNCOMPRESSED_CODEC + heif_error err = heif_image_create((int)actual_w, (int)actual_h, heif_colorspace_nonvisual, heif_chroma_undefined, out_image); + if (err.code != heif_error_Ok) return err; + + uint32_t component_idx; + err = heif_image_add_component(*out_image, (int)actual_w, (int)actual_h, + heif_uncompressed_component_type_monochrome, + heif_channel_datatype_floating_point, 32, &component_idx); + if (err.code != heif_error_Ok) { + heif_image_release(*out_image); + *out_image = nullptr; + return err; + } + + size_t out_stride; + float* out_plane = heif_image_get_component_float32(*out_image, component_idx, &out_stride); + if (!out_plane) { + heif_image_release(*out_image); + *out_image = nullptr; + return {heif_error_Memory_allocation_error, heif_suberror_Unspecified, "Failed to get float plane"}; + } + + tmsize_t tile_buf_size = TIFFTileSize(tif); + std::vector tile_buf(tile_buf_size); + + tmsize_t read = TIFFReadEncodedTile(tif, TIFFComputeTile(tif, tx * m_tile_width, ty * m_tile_height, 0, 0), + tile_buf.data(), tile_buf_size); + if (read < 0) { + heif_image_release(*out_image); + *out_image = nullptr; + return {heif_error_Invalid_input, heif_suberror_Unspecified, "Failed to read TIFF tile"}; + } + + for (uint32_t row = 0; row < actual_h; row++) { + uint8_t* dst = reinterpret_cast(out_plane) + row * out_stride; + float* src = reinterpret_cast(tile_buf.data() + row * m_tile_width * sizeof(float)); + memcpy(dst, src, actual_w * sizeof(float)); + } + + return heif_error_ok; +#else + return {heif_error_Unsupported_feature, heif_suberror_Unspecified, + "Floating point TIFF requires uncompressed codec support (WITH_UNCOMPRESSED_CODEC)."}; +#endif + } + + if (m_sample_format == SAMPLEFORMAT_INT) { +#if WITH_UNCOMPRESSED_CODEC + heif_error err = heif_image_create((int)actual_w, (int)actual_h, heif_colorspace_nonvisual, heif_chroma_undefined, out_image); + if (err.code != heif_error_Ok) return err; + + uint32_t component_idx; + err = heif_image_add_component(*out_image, (int)actual_w, (int)actual_h, + heif_uncompressed_component_type_monochrome, + heif_channel_datatype_signed_integer, m_bits_per_sample, &component_idx); + if (err.code != heif_error_Ok) { + heif_image_release(*out_image); + *out_image = nullptr; + return err; + } + + size_t out_stride; + uint8_t* out_plane; + if (m_bits_per_sample == 8) { + out_plane = reinterpret_cast(heif_image_get_component_int8(*out_image, component_idx, &out_stride)); + } + else { + out_plane = reinterpret_cast(heif_image_get_component_int16(*out_image, component_idx, &out_stride)); + } + if (!out_plane) { + heif_image_release(*out_image); + *out_image = nullptr; + return {heif_error_Memory_allocation_error, heif_suberror_Unspecified, "Failed to get signed int plane"}; + } + + int bytesPerSample = m_bits_per_sample / 8; + tmsize_t tile_buf_size = TIFFTileSize(tif); + std::vector tile_buf(tile_buf_size); + + tmsize_t read = TIFFReadEncodedTile(tif, TIFFComputeTile(tif, tx * m_tile_width, ty * m_tile_height, 0, 0), + tile_buf.data(), tile_buf_size); + if (read < 0) { + heif_image_release(*out_image); + *out_image = nullptr; + return {heif_error_Invalid_input, heif_suberror_Unspecified, "Failed to read TIFF tile"}; + } + + for (uint32_t row = 0; row < actual_h; row++) { + uint8_t* dst = out_plane + row * out_stride; + uint8_t* src = tile_buf.data() + row * m_tile_width * bytesPerSample; + memcpy(dst, src, actual_w * bytesPerSample); + } + + return heif_error_ok; +#else + return {heif_error_Unsupported_feature, heif_suberror_Unspecified, + "Signed integer TIFF requires uncompressed codec support (WITH_UNCOMPRESSED_CODEC)."}; +#endif + } + + int effectiveBitDepth = (m_bits_per_sample <= 8) ? 8 : output_bit_depth; + uint16_t outSpp = (m_samples_per_pixel == 4 && !m_has_alpha) ? 3 : m_samples_per_pixel; + heif_chroma chroma = get_heif_chroma(outSpp, effectiveBitDepth); + heif_colorspace colorspace = (outSpp == 1) ? heif_colorspace_monochrome : heif_colorspace_RGB; + heif_channel channel = (outSpp == 1) ? heif_channel_Y : heif_channel_interleaved; + + heif_error err = heif_image_create((int)actual_w, (int)actual_h, colorspace, chroma, out_image); + if (err.code != heif_error_Ok) return err; + + int planeBitDepth = (effectiveBitDepth <= 8) ? outSpp * 8 : effectiveBitDepth; + heif_image_add_plane(*out_image, channel, (int)actual_w, (int)actual_h, planeBitDepth); + + size_t out_stride; + uint8_t* out_plane = heif_image_get_plane2(*out_image, channel, &out_stride); + + tmsize_t tile_buf_size = TIFFTileSize(tif); + std::vector tile_buf(tile_buf_size); + + if (m_planar_config == PLANARCONFIG_CONTIG) { + tmsize_t read = TIFFReadEncodedTile(tif, TIFFComputeTile(tif, tx * m_tile_width, ty * m_tile_height, 0, 0), + tile_buf.data(), tile_buf_size); + if (read < 0) { + heif_image_release(*out_image); + *out_image = nullptr; + return {heif_error_Invalid_input, heif_suberror_Unspecified, "Failed to read TIFF tile"}; + } + + if (m_bits_per_sample <= 8) { + for (uint32_t row = 0; row < actual_h; row++) { + uint8_t* dst = out_plane + row * out_stride; + uint8_t* src = tile_buf.data() + row * m_tile_width * m_samples_per_pixel; + if (outSpp == m_samples_per_pixel) { + memcpy(dst, src, actual_w * outSpp); + } + else { + for (uint32_t x = 0; x < actual_w; x++) { + memcpy(dst + x * outSpp, src + x * m_samples_per_pixel, outSpp); + } + } + } + } + else if (output_bit_depth <= 8) { + for (uint32_t row = 0; row < actual_h; row++) { + uint8_t* dst = out_plane + row * out_stride; + uint16_t* src = reinterpret_cast(tile_buf.data() + row * m_tile_width * m_samples_per_pixel * 2); + if (outSpp == m_samples_per_pixel) { + for (uint32_t x = 0; x < actual_w * outSpp; x++) { + dst[x] = static_cast(src[x] >> 8); + } + } + else { + for (uint32_t x = 0; x < actual_w; x++) { + for (uint16_t c = 0; c < outSpp; c++) { + dst[x * outSpp + c] = static_cast(src[x * m_samples_per_pixel + c] >> 8); + } + } + } + } + } + else { + int bdShift = 16 - output_bit_depth; + for (uint32_t row = 0; row < actual_h; row++) { + uint16_t* dst = reinterpret_cast(out_plane + row * out_stride); + uint16_t* src = reinterpret_cast(tile_buf.data() + row * m_tile_width * m_samples_per_pixel * 2); + if (outSpp == m_samples_per_pixel) { + if (bdShift == 0) { + memcpy(dst, src, actual_w * outSpp * 2); + } + else { + for (uint32_t x = 0; x < actual_w * outSpp; x++) { + dst[x] = static_cast(src[x] >> bdShift); + } + } + } + else { + for (uint32_t x = 0; x < actual_w; x++) { + for (uint16_t c = 0; c < outSpp; c++) { + dst[x * outSpp + c] = static_cast(src[x * m_samples_per_pixel + c] >> bdShift); + } + } + } + } + } + } + else { + // PLANARCONFIG_SEPARATE + for (uint16_t s = 0; s < outSpp; s++) { + tmsize_t read = TIFFReadEncodedTile(tif, TIFFComputeTile(tif, tx * m_tile_width, ty * m_tile_height, 0, s), + tile_buf.data(), tile_buf_size); + if (read < 0) { + heif_image_release(*out_image); + *out_image = nullptr; + return {heif_error_Invalid_input, heif_suberror_Unspecified, "Failed to read TIFF tile"}; + } + + if (m_bits_per_sample <= 8) { + for (uint32_t row = 0; row < actual_h; row++) { + uint8_t* dst = out_plane + row * out_stride + s; + uint8_t* src = tile_buf.data() + row * m_tile_width; + for (uint32_t x = 0; x < actual_w; x++) { + dst[x * outSpp] = src[x]; + } + } + } + else if (output_bit_depth <= 8) { + for (uint32_t row = 0; row < actual_h; row++) { + uint8_t* dst = out_plane + row * out_stride + s; + uint16_t* src = reinterpret_cast(tile_buf.data() + row * m_tile_width * 2); + for (uint32_t x = 0; x < actual_w; x++) { + dst[x * outSpp] = static_cast(src[x] >> 8); + } + } + } + else { + int bdShift = 16 - output_bit_depth; + for (uint32_t row = 0; row < actual_h; row++) { + uint16_t* dst = reinterpret_cast(out_plane + row * out_stride) + s; + uint16_t* src = reinterpret_cast(tile_buf.data() + row * m_tile_width * 2); + for (uint32_t x = 0; x < actual_w; x++) { + dst[x * outSpp] = static_cast(src[x] >> bdShift); + } + } + } + } + } + + return heif_error_ok; +} + + +void TiledTiffReader::readExif(InputImage* input_image) +{ + TIFF* tif = static_cast(m_tif.get()); + std::unique_ptr tags = ExifTags::Parse(tif); + if (tags) { + tags->Encode(&(input_image->exif)); + } +} + +/* +void TiledTiffReader::printGeoInfo(const char* filename) const +{ +#if HAVE_GEOTIFF + TIFF* tif = XTIFFOpen(filename, "r"); + if (!tif) { + return; + } + + GTIF* gtif = GTIFNew(tif); + if (!gtif) { + XTIFFClose(tif); + return; + } + + // Get EPSG code + unsigned short epsg = 0; + GTIFKeyGetSHORT(gtif, ProjectedCSTypeGeoKey, &epsg, 0, 1); + + // Get CRS citation string + char citation[256] = {}; + GTIFKeyGetASCII(gtif, GTCitationGeoKey, citation, sizeof(citation)); + if (citation[0] == '\0') { + GTIFKeyGetASCII(gtif, GeogCitationGeoKey, citation, sizeof(citation)); + } + + if (epsg > 0 || citation[0] != '\0') { + std::cout << "GeoTIFF: "; + if (epsg > 0) { + std::cout << "EPSG:" << epsg; + } + if (citation[0] != '\0') { + if (epsg > 0) { + std::cout << " (" << citation << ")"; + } + else { + std::cout << citation; + } + } + std::cout << "\n"; + } + + // Get pixel scale + uint16_t scale_count = 0; + double* scale = nullptr; + if (TIFFGetField(tif, TIFFTAG_GEOPIXELSCALE, &scale_count, &scale) && scale_count >= 2) { + std::cout << std::fixed << std::setprecision(3) + << " pixel scale: " << scale[0] << " x " << scale[1] << "\n"; + } + + // Get tiepoint (origin) + uint16_t tp_count = 0; + double* tiepoints = nullptr; + if (TIFFGetField(tif, TIFFTAG_GEOTIEPOINTS, &tp_count, &tiepoints) && tp_count >= 6) { + std::cout << std::fixed << std::setprecision(3) + << " origin: (" << tiepoints[3] << ", " << tiepoints[4] << ")\n"; + } + + GTIFFree(gtif); + XTIFFClose(tif); +#endif + + if (!m_overviews.empty()) { + std::cout << " overviews: "; + for (size_t i = 0; i < m_overviews.size(); i++) { + if (i > 0) std::cout << ", "; + std::cout << m_overviews[i].image_width << "x" << m_overviews[i].image_height; + } + std::cout << "\n"; + } +} +*/ \ No newline at end of file diff --git a/heifio/decoder_tiff.h b/heifio/decoder_tiff.h new file mode 100644 index 0000000000..487ced5eb8 --- /dev/null +++ b/heifio/decoder_tiff.h @@ -0,0 +1,97 @@ +/* + libheif example application "heif". + + MIT License + + Copyright (c) 2024 Joachim Bauch + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#ifndef LIBHEIF_DECODER_TIFF_H +#define LIBHEIF_DECODER_TIFF_H + +#include "decoder.h" +#include "libheif/heif.h" +#include +#include +#include + +struct YCbCrInfo { + bool is_ycbcr = false; + uint16_t horiz_sub = 1; // horizontal subsampling factor + uint16_t vert_sub = 1; // vertical subsampling factor +}; + +LIBHEIF_API +heif_error loadTIFF(const char *filename, int output_bit_depth, InputImage *input_image); + +class LIBHEIF_API TiledTiffReader { +public: + ~TiledTiffReader(); + + struct OverviewInfo { + uint32_t dir_index; + uint32_t image_width; + uint32_t image_height; + uint32_t tile_width; + uint32_t tile_height; + }; + + // Returns a reader if the file is a tiled TIFF. If the TIFF is not tiled, + // returns nullptr with heif_error_Ok (caller should fall back to loadTIFF). + static std::unique_ptr open(const char* filename, heif_error* out_err); + + uint32_t imageWidth() const { return m_image_width; } + uint32_t imageHeight() const { return m_image_height; } + uint32_t tileWidth() const { return m_tile_width; } + uint32_t tileHeight() const { return m_tile_height; } + uint32_t nColumns() const { return m_n_columns; } + uint32_t nRows() const { return m_n_rows; } + + const std::vector& overviews() const { return m_overviews; } + bool setDirectory(uint32_t dir_index); + + uint16_t bitsPerSample() const { return m_bits_per_sample; } + uint16_t sampleFormat() const { return m_sample_format; } + + heif_error readTile(uint32_t tx, uint32_t ty, int output_bit_depth, heif_image** out_image); + void readExif(InputImage* input_image); + //void printGeoInfo(const char* filename) const; + +private: + TiledTiffReader() = default; + + struct TiffCloser { void operator()(void* tif) const; }; + std::unique_ptr m_tif; + + uint32_t m_image_width = 0, m_image_height = 0; + uint32_t m_tile_width = 0, m_tile_height = 0; + uint32_t m_n_columns = 0, m_n_rows = 0; + uint16_t m_samples_per_pixel = 0; + uint16_t m_bits_per_sample = 0; + uint16_t m_planar_config = 0; + uint16_t m_sample_format = 1; // SAMPLEFORMAT_UINT + bool m_has_alpha = false; + YCbCrInfo m_ycbcr; + + std::vector m_overviews; +}; + +#endif // LIBHEIF_DECODER_TIFF_H diff --git a/heifio/decoder_y4m.cc b/heifio/decoder_y4m.cc new file mode 100644 index 0000000000..599e86419a --- /dev/null +++ b/heifio/decoder_y4m.cc @@ -0,0 +1,149 @@ +/* + libheif example application "heif". + + MIT License + + Copyright (c) 2023 Dirk Farin + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#include "decoder_y4m.h" +#include +#include +#include +#include + +static struct heif_error heif_error_ok = {heif_error_Ok, heif_suberror_Unspecified, "Success"}; + +heif_error loadY4M(const char *filename, InputImage *input_image) +{ + + struct heif_image* image = nullptr; + + + // open input file + + std::ifstream istr(filename, std::ios_base::binary); + if (istr.fail()) { + struct heif_error err = { + .code = heif_error_Invalid_input, + .subcode = heif_suberror_Unspecified, + .message = "Cannot open Y4M file"}; + return err; + } + + + std::string header; + getline(istr, header); + + if (header.find("YUV4MPEG2 ") != 0) { + struct heif_error err = { + .code = heif_error_Unsupported_feature, + .subcode = heif_suberror_Unspecified, + .message = "Input is not a Y4M file."}; + return err; + } + + int w = -1; + int h = -1; + + size_t pos = 0; + for (;;) { + pos = header.find(' ', pos + 1) + 1; + if (pos == std::string::npos) { + break; + } + + size_t end = header.find_first_of(" \n", pos + 1); + if (end == std::string::npos) { + break; + } + + if (end - pos <= 1) { + struct heif_error err = { + .code = heif_error_Unsupported_feature, + .subcode = heif_suberror_Unspecified, + .message = "Header format error in Y4M file."}; + return err; + } + + char tag = header[pos]; + std::string value = header.substr(pos + 1, end - pos - 1); + if (tag == 'W') { + w = atoi(value.c_str()); + } + else if (tag == 'H') { + h = atoi(value.c_str()); + } + } + + std::string frameheader; + getline(istr, frameheader); + + if (frameheader != "FRAME") { + struct heif_error err = { + .code = heif_error_Unsupported_feature, + .subcode = heif_suberror_Unspecified, + .message = "Y4M misses the frame header."}; + return err; + } + + if (w < 0 || h < 0) { + struct heif_error err = { + .code = heif_error_Unsupported_feature, + .subcode = heif_suberror_Unspecified, + .message = "Y4M has invalid frame size."}; + return err; + } + + struct heif_error err = heif_image_create(w, h, + heif_colorspace_YCbCr, + heif_chroma_420, + &image); + if (err.code != heif_error_Ok) { + return err; + } + + heif_image_add_plane(image, heif_channel_Y, w, h, 8); + heif_image_add_plane(image, heif_channel_Cb, (w + 1) / 2, (h + 1) / 2, 8); + heif_image_add_plane(image, heif_channel_Cr, (w + 1) / 2, (h + 1) / 2, 8); + + size_t y_stride, cb_stride, cr_stride; + uint8_t* py = heif_image_get_plane2(image, heif_channel_Y, &y_stride); + uint8_t* pcb = heif_image_get_plane2(image, heif_channel_Cb, &cb_stride); + uint8_t* pcr = heif_image_get_plane2(image, heif_channel_Cr, &cr_stride); + + for (int y = 0; y < h; y++) { + istr.read((char*) (py + y * y_stride), w); + } + + for (int y = 0; y < (h + 1) / 2; y++) { + istr.read((char*) (pcb + y * cb_stride), (w + 1) / 2); + } + + for (int y = 0; y < (h + 1) / 2; y++) { + istr.read((char*) (pcr + y * cr_stride), (w + 1) / 2); + } + + input_image->image = std::shared_ptr(image, + [](heif_image* img) { heif_image_release(img); }); + + return heif_error_ok; +} diff --git a/heifio/decoder_y4m.h b/heifio/decoder_y4m.h new file mode 100644 index 0000000000..91a7a5aadf --- /dev/null +++ b/heifio/decoder_y4m.h @@ -0,0 +1,43 @@ +/* + libheif example application "heif". + + MIT License + + Copyright (c) 2023 Dirk Farin + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#ifndef LIBHEIF_DECODER_Y4M_H +#define LIBHEIF_DECODER_Y4M_H + +#include "decoder.h" + +#ifdef __cplusplus +extern "C" { +#endif + +LIBHEIF_API +heif_error loadY4M(const char *filename, InputImage *input_image); + +#ifdef __cplusplus +} +#endif + +#endif //LIBHEIF_DECODER_Y4M_H diff --git a/examples/encoder.cc b/heifio/encoder.cc similarity index 70% rename from examples/encoder.cc rename to heifio/encoder.cc index 3ba70673ea..756696cba2 100644 --- a/examples/encoder.cc +++ b/heifio/encoder.cc @@ -1,6 +1,6 @@ /* - libheif example application "convert". - This file is part of convert, an example application using libheif. + libheif example application. + This file is part of heif-dec, an example application using libheif. MIT License @@ -24,11 +24,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#if defined(HAVE_CONFIG_H) -#include "config.h" -#endif #include +#include #include "encoder.h" @@ -70,3 +68,31 @@ uint8_t* Encoder::GetExifMetaData(const struct heif_image_handle* handle, size_t return nullptr; } + + +std::vector Encoder::get_xmp_metadata(const struct heif_image_handle* handle) +{ + std::vector xmp; + + heif_item_id metadata_ids[16]; + int count = heif_image_handle_get_list_of_metadata_block_IDs(handle, nullptr, metadata_ids, 16); + + for (int i = 0; i < count; i++) { + if (strcmp(heif_image_handle_get_metadata_type(handle, metadata_ids[i]), "mime") == 0 && + strcmp(heif_image_handle_get_metadata_content_type(handle, metadata_ids[i]), "application/rdf+xml") == 0) { + + size_t datasize = heif_image_handle_get_metadata_size(handle, metadata_ids[i]); + xmp.resize(datasize); + + heif_error error = heif_image_handle_get_metadata(handle, metadata_ids[i], xmp.data()); + if (error.code != heif_error_Ok) { + // TODO: return error + return {}; + } + + return xmp; + } + } + + return {}; +} diff --git a/examples/encoder.h b/heifio/encoder.h similarity index 67% rename from examples/encoder.h rename to heifio/encoder.h index e1afd35782..04579d3f17 100644 --- a/examples/encoder.h +++ b/heifio/encoder.h @@ -1,9 +1,10 @@ /* - libheif example application "convert". + libheif example application. MIT License Copyright (c) 2017 struktur AG, Joachim Bauch + Copyright (c) 2023 Dirk Farin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -29,31 +30,36 @@ #include #include -#include +#include "libheif/heif.h" +#include + class Encoder { public: - virtual ~Encoder() - {} + virtual ~Encoder() = default; virtual heif_colorspace colorspace(bool has_alpha) const = 0; virtual heif_chroma chroma(bool has_alpha, int bit_depth) const = 0; - virtual void UpdateDecodingOptions(const struct heif_image_handle* handle, - struct heif_decoding_options* options) const + virtual bool supports_alpha() const = 0; + + virtual void UpdateDecodingOptions(const heif_image_handle* handle, + heif_decoding_options* options) const { // Override if necessary. } - virtual bool Encode(const struct heif_image_handle* handle, - const struct heif_image* image, const std::string& filename) = 0; + virtual bool Encode(const heif_image_handle* handle, + const heif_image* image, const std::string& filename) = 0; protected: - static bool HasExifMetaData(const struct heif_image_handle* handle); + static bool HasExifMetaData(const heif_image_handle* handle); + + static uint8_t* GetExifMetaData(const heif_image_handle* handle, size_t* size); - static uint8_t* GetExifMetaData(const struct heif_image_handle* handle, size_t* size); + static std::vector get_xmp_metadata(const heif_image_handle* handle); }; #endif // EXAMPLE_ENCODER_H diff --git a/examples/encoder_jpeg.cc b/heifio/encoder_jpeg.cc similarity index 57% rename from examples/encoder_jpeg.cc rename to heifio/encoder_jpeg.cc index 64c772458f..c38d4cb4bc 100644 --- a/examples/encoder_jpeg.cc +++ b/heifio/encoder_jpeg.cc @@ -1,5 +1,5 @@ /* - libheif example application "convert". + libheif example application. MIT License @@ -23,17 +23,31 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -#if defined(HAVE_CONFIG_H) -#include "config.h" -#endif -#include -#include -#include +#include +#include -#include +#include +#include #include "encoder_jpeg.h" +#include "exif.h" + +#include + +#include "common_utils.h" + +#define JPEG_XMP_MARKER (JPEG_APP0+1) /* JPEG marker code for XMP */ +#define JPEG_XMP_MARKER_ID "http://ns.adobe.com/xap/1.0/" + + +struct ErrorHandler +{ + struct jpeg_error_mgr pub; /* "public" fields */ + jmp_buf setjmp_buffer; /* for return to caller */ +}; + + JpegEncoder::JpegEncoder(int quality) : quality_(quality) { @@ -45,25 +59,22 @@ JpegEncoder::JpegEncoder(int quality) : quality_(quality) void JpegEncoder::UpdateDecodingOptions(const struct heif_image_handle* handle, struct heif_decoding_options* options) const { - if (HasExifMetaData(handle)) { - options->ignore_transformations = 1; - } - options->convert_hdr_to_8bit = 1; } // static -void JpegEncoder::OnJpegError(j_common_ptr cinfo) +static void OnJpegError(j_common_ptr cinfo) { ErrorHandler* handler = reinterpret_cast(cinfo->err); longjmp(handler->setjmp_buffer, 1); } +#define MAX_BYTES_IN_MARKER 65533 /* maximum data len of a JPEG marker */ + #if !defined(HAVE_JPEG_WRITE_ICC_PROFILE) #define ICC_MARKER (JPEG_APP0 + 2) /* JPEG marker code for ICC */ #define ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */ -#define MAX_BYTES_IN_MARKER 65533 /* maximum data len of a JPEG marker */ #define MAX_DATA_BYTES_IN_MARKER (MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN) /* @@ -143,7 +154,7 @@ bool JpegEncoder::Encode(const struct heif_image_handle* handle, struct jpeg_compress_struct cinfo; struct ErrorHandler jerr; cinfo.err = jpeg_std_error(reinterpret_cast(&jerr)); - jerr.pub.error_exit = &JpegEncoder::OnJpegError; + jerr.pub.error_exit = &OnJpegError; if (setjmp(jerr.setjmp_buffer)) { cinfo.err->output_message(reinterpret_cast(&cinfo)); jpeg_destroy_compress(&cinfo); @@ -164,39 +175,123 @@ bool JpegEncoder::Encode(const struct heif_image_handle* handle, static const boolean kWriteAllTables = TRUE; jpeg_start_compress(&cinfo, kWriteAllTables); - size_t exifsize = 0; - uint8_t* exifdata = GetExifMetaData(handle, &exifsize); - if (exifdata && exifsize > 4) { - static const uint8_t kExifMarker = JPEG_APP0 + 1; - jpeg_write_marker(&cinfo, kExifMarker, exifdata + 4, - static_cast(exifsize - 4)); - free(exifdata); + // --- Write EXIF + + if (handle) { + size_t exifsize = 0; + uint8_t* exifdata = GetExifMetaData(handle, &exifsize); + if (exifdata) { + if (exifsize > 4) { + static const uint8_t kExifMarker = JPEG_APP0 + 1; + + uint32_t skip = four_bytes_to_uint32(exifdata[0], exifdata[1], exifdata[2], exifdata[3]); + if (skip > (exifsize - 4)) { + fprintf(stderr, "Invalid EXIF data (offset too large)\n"); + free(exifdata); + jpeg_destroy_compress(&cinfo); + fclose(fp); + return false; + } + skip += 4; + + uint8_t* ptr = exifdata + skip; + size_t size = exifsize - skip; + + if (size > std::numeric_limits::max()) { + fprintf(stderr, "EXIF larger than 4GB is not supported"); + free(exifdata); + jpeg_destroy_compress(&cinfo); + fclose(fp); + return false; + } + + auto size32 = static_cast(size); + + // libheif by default normalizes the image orientation, so that we have to set the EXIF Orientation to "Horizontal (normal)" + modify_exif_orientation_tag_if_it_exists(ptr, size32, 1); + overwrite_exif_image_size_if_it_exists(ptr, size32, cinfo.image_width, cinfo.image_height); + + // We have to limit the size for the memcpy, otherwise GCC warns that we exceed the maximum size. + if (size > 0x1000000) { + size = 0x1000000; + } + + std::vector jpegExifMarkerData(6 + size); + memcpy(jpegExifMarkerData.data() + 6, ptr, size); + jpegExifMarkerData[0] = 'E'; + jpegExifMarkerData[1] = 'x'; + jpegExifMarkerData[2] = 'i'; + jpegExifMarkerData[3] = 'f'; + jpegExifMarkerData[4] = 0; + jpegExifMarkerData[5] = 0; + + ptr = jpegExifMarkerData.data(); + size = jpegExifMarkerData.size(); + + while (size > MAX_BYTES_IN_MARKER) { + jpeg_write_marker(&cinfo, kExifMarker, ptr, + static_cast(MAX_BYTES_IN_MARKER)); + + ptr += MAX_BYTES_IN_MARKER; + size -= MAX_BYTES_IN_MARKER; + } + + jpeg_write_marker(&cinfo, kExifMarker, ptr, + static_cast(size)); + } + + free(exifdata); + } } - size_t profile_size = heif_image_handle_get_raw_color_profile_size(handle); - if (profile_size > 0) { - uint8_t* profile_data = static_cast(malloc(profile_size)); - heif_image_handle_get_raw_color_profile(handle, profile_data); - jpeg_write_icc_profile(&cinfo, profile_data, (unsigned int) profile_size); - free(profile_data); - } + // --- Write XMP + // spec: https://raw.githubusercontent.com/adobe/xmp-docs/master/XMPSpecifications/XMPSpecificationPart3.pdf - if (heif_image_get_bits_per_pixel(image, heif_channel_Y) != 8) { - fprintf(stderr, "JPEG writer cannot handle image with >8 bpp.\n"); - return false; + if (handle) { + auto xmp = get_xmp_metadata(handle); + if (xmp.size() > 65502) { + fprintf(stderr, "XMP data too large, ExtendedXMP is not supported yet.\n"); + } + else if (!xmp.empty()) { + std::vector xmpWithId; + xmpWithId.resize(xmp.size() + strlen(JPEG_XMP_MARKER_ID) + 1); + strcpy((char*) xmpWithId.data(), JPEG_XMP_MARKER_ID); + memcpy(xmpWithId.data() + strlen(JPEG_XMP_MARKER_ID) + 1, xmp.data(), xmp.size()); + + jpeg_write_marker(&cinfo, JPEG_XMP_MARKER, xmpWithId.data(), static_cast(xmpWithId.size())); + } } + // --- Write ICC + + if (handle) { + size_t profile_size = heif_image_handle_get_raw_color_profile_size(handle); + if (profile_size > 0) { + uint8_t* profile_data = static_cast(malloc(profile_size)); + heif_image_handle_get_raw_color_profile(handle, profile_data); + jpeg_write_icc_profile(&cinfo, profile_data, (unsigned int) profile_size); + free(profile_data); + } + + + if (heif_image_get_bits_per_pixel(image, heif_channel_Y) != 8) { + fprintf(stderr, "JPEG writer cannot handle image with >8 bpp.\n"); + jpeg_destroy_compress(&cinfo); + fclose(fp); + return false; + } + } - int stride_y; - const uint8_t* row_y = heif_image_get_plane_readonly(image, heif_channel_Y, - &stride_y); - int stride_u; - const uint8_t* row_u = heif_image_get_plane_readonly(image, heif_channel_Cb, - &stride_u); - int stride_v; - const uint8_t* row_v = heif_image_get_plane_readonly(image, heif_channel_Cr, - &stride_v); + size_t stride_y; + const uint8_t* row_y = heif_image_get_plane_readonly2(image, heif_channel_Y, + &stride_y); + size_t stride_u; + const uint8_t* row_u = heif_image_get_plane_readonly2(image, heif_channel_Cb, + &stride_u); + size_t stride_v; + const uint8_t* row_v = heif_image_get_plane_readonly2(image, heif_channel_Cr, + &stride_v); JSAMPARRAY buffer = cinfo.mem->alloc_sarray( reinterpret_cast(&cinfo), JPOOL_IMAGE, diff --git a/examples/encoder_jpeg.h b/heifio/encoder_jpeg.h similarity index 75% rename from examples/encoder_jpeg.h rename to heifio/encoder_jpeg.h index c6b02966e6..a571ba901c 100644 --- a/examples/encoder_jpeg.h +++ b/heifio/encoder_jpeg.h @@ -1,9 +1,10 @@ /* - libheif example application "convert". + libheif example application. MIT License Copyright (c) 2017 struktur AG, Joachim Bauch + Copyright (c) 2023 Dirk Farin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -30,8 +31,6 @@ #include #include -#include - #include #include "encoder.h" @@ -51,23 +50,17 @@ class JpegEncoder : public Encoder return heif_chroma_420; } - void UpdateDecodingOptions(const struct heif_image_handle* handle, - struct heif_decoding_options* options) const override; + bool supports_alpha() const override { return false; } + + void UpdateDecodingOptions(const heif_image_handle* handle, + heif_decoding_options* options) const override; - bool Encode(const struct heif_image_handle* handle, - const struct heif_image* image, const std::string& filename) override; + bool Encode(const heif_image_handle* handle, + const heif_image* image, const std::string& filename) override; private: static const int kDefaultQuality = 90; - struct ErrorHandler - { - struct jpeg_error_mgr pub; /* "public" fields */ - jmp_buf setjmp_buffer; /* for return to caller */ - }; - - static void OnJpegError(j_common_ptr cinfo); - int quality_; }; diff --git a/heifio/encoder_png.cc b/heifio/encoder_png.cc new file mode 100644 index 0000000000..9f82466ecf --- /dev/null +++ b/heifio/encoder_png.cc @@ -0,0 +1,247 @@ +/* + libheif example application. + + MIT License + + Copyright (c) 2017 struktur AG, Joachim Bauch + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#include +#include +#include +#include +#include + +#include "encoder_png.h" + +#include "common_utils.h" +#include "exif.h" + +PngEncoder::PngEncoder() = default; + +// Returns false if ICC profile recognized invalid and could not be fixed. +bool fix_icc_profile(uint8_t* profile_data, uint32_t& profile_size) +{ + if (profile_size < 128) { + return false; + } + + + // --- check that profile size specified in header matches the real size + + uint32_t size_in_header = four_bytes_to_uint32(profile_data[0], + profile_data[1], + profile_data[2], + profile_data[3]); + + if (size_in_header != profile_size) { + + // Size in header is smaller than actual size, but alignment indicates that it might + // be correct. Replace real data length with size in header. + if (size_in_header < profile_size && (size_in_header & 3)==0) { + fprintf(stderr, "Input ICC profile has wrong size in header (%d instead of %d). Skipping extra bytes at the end. " + "Note that this may still be incorrect and the ICC profile may be broken.\n", size_in_header, profile_size); + + profile_size = size_in_header; + } + else { + return false; + } + } + + return true; +} + + +bool PngEncoder::Encode(const heif_image_handle* handle, + const heif_image* image, const std::string& filename) +{ + png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, nullptr, + nullptr, nullptr); + if (!png_ptr) { + fprintf(stderr, "libpng initialization failed (1)\n"); + return false; + } + + png_infop info_ptr = png_create_info_struct(png_ptr); + if (!info_ptr) { + png_destroy_write_struct(&png_ptr, nullptr); + fprintf(stderr, "libpng initialization failed (2)\n"); + return false; + } + + if (m_compression_level != -1) { + png_set_compression_level(png_ptr, m_compression_level); + } + + FILE* fp = fopen(filename.c_str(), "wb"); + if (!fp) { + fprintf(stderr, "Can't open %s: %s\n", filename.c_str(), strerror(errno)); + png_destroy_write_struct(&png_ptr, &info_ptr); + return false; + } + + if (setjmp(png_jmpbuf(png_ptr))) { + png_destroy_write_struct(&png_ptr, &info_ptr); + fclose(fp); + fprintf(stderr, "Error while encoding image\n"); + return false; + } + + png_init_io(png_ptr, fp); + + bool withAlpha = (heif_image_get_chroma_format(image) == heif_chroma_interleaved_RGBA || + heif_image_get_chroma_format(image) == heif_chroma_interleaved_RRGGBBAA_BE); + + int width = heif_image_get_width(image, heif_channel_interleaved); + int height = heif_image_get_height(image, heif_channel_interleaved); + + int bitDepth; + int input_bpp = heif_image_get_bits_per_pixel_range(image, heif_channel_interleaved); + if (input_bpp > 8) { + bitDepth = 16; + } + else { + bitDepth = 8; + } + + const int colorType = withAlpha ? PNG_COLOR_TYPE_RGBA : PNG_COLOR_TYPE_RGB; + + png_set_IHDR(png_ptr, info_ptr, width, height, bitDepth, colorType, + PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE); + + // --- write ICC profile + + if (handle) { + uint32_t profile_size = static_cast(heif_image_handle_get_raw_color_profile_size(handle)); + if (profile_size > 0) { + uint8_t* profile_data = static_cast(malloc(profile_size)); + heif_image_handle_get_raw_color_profile(handle, profile_data); + if (fix_icc_profile(profile_data, profile_size)) { + char profile_name[] = "unknown"; + png_set_iCCP(png_ptr, info_ptr, profile_name, PNG_COMPRESSION_TYPE_BASE, +#if PNG_LIBPNG_VER < 10500 + (png_charp)profile_data, +#else + (png_const_bytep) profile_data, +#endif + (png_uint_32) profile_size); + } + else { + fprintf(stderr, "Invalid ICC profile. Writing PNG file without ICC.\n"); + } + free(profile_data); + } + } + + // --- write EXIF metadata + +#ifdef PNG_eXIf_SUPPORTED + if (handle) { + size_t exifsize = 0; + uint8_t* exifdata = GetExifMetaData(handle, &exifsize); + if (exifdata) { + if (exifsize > 4) { + uint32_t skip = four_bytes_to_uint32(exifdata[0], exifdata[1], exifdata[2], exifdata[3]); + if (skip < (exifsize - 4)) { + skip += 4; + uint8_t* ptr = exifdata + skip; + size_t size = exifsize - skip; + + // libheif by default normalizes the image orientation, so that we have to set the EXIF Orientation to "Horizontal (normal)" + modify_exif_orientation_tag_if_it_exists(ptr, (int) size, 1); + overwrite_exif_image_size_if_it_exists(ptr, (int) size, width, height); + + png_set_eXIf_1(png_ptr, info_ptr, (png_uint_32) size, ptr); + } + } + + free(exifdata); + } + } +#endif + + // --- write XMP metadata + +#ifdef PNG_iTXt_SUPPORTED + if (handle) { + // spec: https://raw.githubusercontent.com/adobe/xmp-docs/master/XMPSpecifications/XMPSpecificationPart3.pdf + std::vector xmp = get_xmp_metadata(handle); + if (!xmp.empty()) { + // make sure that XMP string is always null terminated. + if (xmp.back() != 0) { + xmp.push_back(0); + } + + // compute XMP string length + size_t text_length = 0; + while (xmp[text_length] != 0) { + text_length++; + } + + png_text xmp_text{}; // important to zero-initialize the structure so that the remaining fields are NULL ! + xmp_text.compression = PNG_ITXT_COMPRESSION_NONE; + xmp_text.key = (char*) "XML:com.adobe.xmp"; + xmp_text.text = (char*) xmp.data(); + xmp_text.text_length = 0; // should be 0 for ITXT according the libpng documentation + xmp_text.itxt_length = text_length; + png_set_text(png_ptr, info_ptr, &xmp_text, 1); + } + } +#endif + + png_write_info(png_ptr, info_ptr); + + uint8_t** row_pointers = new uint8_t* [height]; + + size_t stride_rgb; + const uint8_t* row_rgb = heif_image_get_plane_readonly2(image, + heif_channel_interleaved, &stride_rgb); + + for (int y = 0; y < height; ++y) { + row_pointers[y] = const_cast(&row_rgb[y * stride_rgb]); + } + + if (bitDepth == 16) { + // shift image data to full 16bit range + + int shift = 16 - input_bpp; + if (shift > 0) { + for (int y = 0; y < height; ++y) { + for (size_t x = 0; x < stride_rgb; x += 2) { + uint8_t* p = (&row_pointers[y][x]); + int v = (p[0] << 8) | p[1]; + v = (v << shift) | (v >> (16 - shift)); + p[0] = (uint8_t) (v >> 8); + p[1] = (uint8_t) (v & 0xFF); + } + } + } + } + + + png_write_image(png_ptr, row_pointers); + + png_write_end(png_ptr, nullptr); + png_destroy_write_struct(&png_ptr, &info_ptr); + delete[] row_pointers; + fclose(fp); + return true; +} diff --git a/examples/encoder_png.h b/heifio/encoder_png.h similarity index 78% rename from examples/encoder_png.h rename to heifio/encoder_png.h index 61d794e927..ddc1afdea4 100644 --- a/examples/encoder_png.h +++ b/heifio/encoder_png.h @@ -1,9 +1,10 @@ /* - libheif example application "convert". + libheif example application. MIT License Copyright (c) 2017 struktur AG, Joachim Bauch + Copyright (c) 2023 Dirk Farin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -35,6 +36,13 @@ class PngEncoder : public Encoder public: PngEncoder(); + // 0 = fastest compression + // 9 = best compression + // -1 = zlib default + void set_compression_level(int level) { + m_compression_level = level; + } + heif_colorspace colorspace(bool has_alpha) const override { return heif_colorspace_RGB; @@ -42,7 +50,7 @@ class PngEncoder : public Encoder heif_chroma chroma(bool has_alpha, int bit_depth) const override { - if (bit_depth == 8) { + if (bit_depth <= 8) { if (has_alpha) return heif_chroma_interleaved_RGBA; else @@ -56,10 +64,13 @@ class PngEncoder : public Encoder } } - bool Encode(const struct heif_image_handle* handle, - const struct heif_image* image, const std::string& filename) override; + bool supports_alpha() const override { return true; } + + bool Encode(const heif_image_handle* handle, + const heif_image* image, const std::string& filename) override; private: + int m_compression_level = -1; }; #endif // EXAMPLE_ENCODER_PNG_H diff --git a/heifio/encoder_tiff.cc b/heifio/encoder_tiff.cc new file mode 100644 index 0000000000..1dc51e3ca1 --- /dev/null +++ b/heifio/encoder_tiff.cc @@ -0,0 +1,77 @@ +/* + libheif example application. + + MIT License + + Copyright (c) 2024 Brad Hards + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#include +#include +#include +#include +#include + +#include "encoder_tiff.h" +#include + +TiffEncoder::TiffEncoder() = default; + +bool TiffEncoder::Encode(const struct heif_image_handle *handle, + const struct heif_image *image, const std::string &filename) +{ + TIFF *tif = TIFFOpen(filename.c_str(), "w"); + + // For now we write interleaved + int width = heif_image_get_width(image, heif_channel_interleaved); + int height = heif_image_get_height(image, heif_channel_interleaved); + bool hasAlpha = ((heif_image_get_chroma_format(image) == heif_chroma_interleaved_RGBA) || + (heif_image_get_chroma_format(image) == heif_chroma_interleaved_RRGGBBAA_BE)); + int input_bpp = heif_image_get_bits_per_pixel_range(image, heif_channel_interleaved); + + TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width); + TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height); + TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, input_bpp); + TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, hasAlpha ? 4 : 3); + if (hasAlpha) + { + // TODO: is alpha premultiplied? + uint16_t extra_samples[1] = {EXTRASAMPLE_UNASSALPHA}; + TIFFSetField(tif, TIFFTAG_EXTRASAMPLES, 1, &extra_samples); + } + TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, 1); + TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT); + TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG); + TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, PHOTOMETRIC_RGB); + TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_UINT); + TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE); + + size_t stride_rgb; + const uint8_t *row_rgb = heif_image_get_plane_readonly2(image, + heif_channel_interleaved, &stride_rgb); + + for (int i = 0; i < height; i++) + { + // memcpy(scan_line, &buffer[i * width], width * sizeof(uint32)); + TIFFWriteScanline(tif, (void *)(&(row_rgb[i * stride_rgb])), i, 0); + } + TIFFClose(tif); + return true; +} diff --git a/heifio/encoder_tiff.h b/heifio/encoder_tiff.h new file mode 100644 index 0000000000..c9160940c9 --- /dev/null +++ b/heifio/encoder_tiff.h @@ -0,0 +1,66 @@ +/* + libheif example application. + + MIT License + + Copyright (c) 2024 Brad Hards + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ +#ifndef EXAMPLE_ENCODER_TIFF_H +#define EXAMPLE_ENCODER_TIFF_H + +#include + +#include "encoder.h" + +class TiffEncoder : public Encoder +{ +public: + TiffEncoder(); + + + heif_colorspace colorspace(bool has_alpha) const override + { + return heif_colorspace_RGB; + } + + heif_chroma chroma(bool has_alpha, int bit_depth) const override + { + if (bit_depth == 8) { + if (has_alpha) + return heif_chroma_interleaved_RGBA; + else + return heif_chroma_interleaved_RGB; + } + else { + if (has_alpha) + return heif_chroma_interleaved_RRGGBBAA_BE; + else + return heif_chroma_interleaved_RRGGBB_BE; + } + } + + bool supports_alpha() const override { return true; } + + bool Encode(const heif_image_handle* handle, + const heif_image* image, const std::string& filename) override; +}; + +#endif // EXAMPLE_ENCODER_TIFF_H diff --git a/examples/encoder_y4m.cc b/heifio/encoder_y4m.cc similarity index 79% rename from examples/encoder_y4m.cc rename to heifio/encoder_y4m.cc index a2340072ad..b672166fcf 100644 --- a/examples/encoder_y4m.cc +++ b/heifio/encoder_y4m.cc @@ -1,9 +1,9 @@ /* - libheif example application "convert". + libheif example application. MIT License - Copyright (c) 2019 struktur AG, Dirk Farin + Copyright (c) 2019 Dirk Farin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -25,19 +25,18 @@ */ #include "encoder_y4m.h" -#include -#include -#include +#include +#include +#include -Y4MEncoder::Y4MEncoder() -{ -} +Y4MEncoder::Y4MEncoder() = default; void Y4MEncoder::UpdateDecodingOptions(const struct heif_image_handle* handle, struct heif_decoding_options* options) const { + options->convert_hdr_to_8bit = 1; } @@ -51,10 +50,10 @@ bool Y4MEncoder::Encode(const struct heif_image_handle* handle, return false; } - int y_stride, cb_stride, cr_stride; - const uint8_t* yp = heif_image_get_plane_readonly(image, heif_channel_Y, &y_stride); - const uint8_t* cbp = heif_image_get_plane_readonly(image, heif_channel_Cb, &cb_stride); - const uint8_t* crp = heif_image_get_plane_readonly(image, heif_channel_Cr, &cr_stride); + size_t y_stride, cb_stride, cr_stride; + const uint8_t* yp = heif_image_get_plane_readonly2(image, heif_channel_Y, &y_stride); + const uint8_t* cbp = heif_image_get_plane_readonly2(image, heif_channel_Cb, &cb_stride); + const uint8_t* crp = heif_image_get_plane_readonly2(image, heif_channel_Cr, &cr_stride); assert(y_stride > 0); assert(cb_stride > 0); @@ -65,6 +64,11 @@ bool Y4MEncoder::Encode(const struct heif_image_handle* handle, int cw = heif_image_get_width(image, heif_channel_Cb); int ch = heif_image_get_height(image, heif_channel_Cb); + if (yw < 0 || cw < 0) { + fclose(fp); + return false; + } + fprintf(fp, "YUV4MPEG2 W%d H%d F30:1\nFRAME\n", yw, yh); for (int y = 0; y < yh; y++) { diff --git a/examples/encoder_y4m.h b/heifio/encoder_y4m.h similarity index 78% rename from examples/encoder_y4m.h rename to heifio/encoder_y4m.h index d8d0e1dc66..54da8deb8e 100644 --- a/examples/encoder_y4m.h +++ b/heifio/encoder_y4m.h @@ -1,9 +1,9 @@ /* - libheif example application "convert". + libheif example application. MIT License - Copyright (c) 2019 struktur AG, Dirk Farin + Copyright (c) 2019 Dirk Farin Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -45,11 +45,13 @@ class Y4MEncoder : public Encoder return heif_chroma_420; } - void UpdateDecodingOptions(const struct heif_image_handle* handle, - struct heif_decoding_options* options) const override; + bool supports_alpha() const override { return false; } - bool Encode(const struct heif_image_handle* handle, - const struct heif_image* image, const std::string& filename) override; + void UpdateDecodingOptions(const heif_image_handle* handle, + heif_decoding_options* options) const override; + + bool Encode(const heif_image_handle* handle, + const heif_image* image, const std::string& filename) override; private: }; diff --git a/heifio/exif.cc b/heifio/exif.cc new file mode 100644 index 0000000000..e0ffd88946 --- /dev/null +++ b/heifio/exif.cc @@ -0,0 +1,263 @@ +/* + * HEIF codec. + * Copyright (c) 2022 Dirk Farin + * + * This file is part of libheif. + * + * libheif is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * libheif is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libheif. If not, see . + */ + +#include +#include "exif.h" + +#include "common_utils.h" + +#define EXIF_TYPE_SHORT 3 +#define EXIF_TYPE_LONG 4 +#define DEFAULT_EXIF_ORIENTATION 1 +#define EXIF_TAG_ORIENTATION 0x112 +#define EXIF_TAG_IMAGE_WIDTH ((uint16_t)0x0100) +#define EXIF_TAG_IMAGE_HEIGHT ((uint16_t)0x0101) +#define EXIF_TAG_VALID_IMAGE_WIDTH ((uint16_t)0xA002) +#define EXIF_TAG_VALID_IMAGE_HEIGHT ((uint16_t)0xA003) +#define EXIF_TAG_EXIF_IFD_POINTER ((uint16_t)0x8769) + +// Note: As far as I can see, it is not defined in the EXIF standard whether the offsets and counts of the IFD is signed or unsigned. +// We assume that these are all unsigned. + +static uint32_t read32(const uint8_t* data, uint32_t size, uint32_t pos, bool littleEndian) +{ + assert(pos <= size - 4); + + const uint8_t* p = data + pos; + + if (littleEndian) { + return four_bytes_to_uint32(p[3], p[2], p[1], p[0]); + } + else { + return four_bytes_to_uint32(p[0], p[1], p[2], p[3]); + } +} + + +static uint16_t read16(const uint8_t* data, uint32_t size, uint32_t pos, bool littleEndian) +{ + assert(pos <= size - 2); + + const uint8_t* p = data + pos; + + if (littleEndian) { + return two_bytes_to_uint16(p[1], p[0]); + } + else { + return two_bytes_to_uint16(p[0], p[1]); + } +} + + +static void write16(uint8_t* data, uint32_t size, uint32_t pos, uint16_t value, bool littleEndian) +{ + assert(pos <= size - 2); + + uint8_t* p = data + pos; + + if (littleEndian) { + p[0] = (uint8_t) (value & 0xFF); + p[1] = (uint8_t) (value >> 8); + } + else { + p[0] = (uint8_t) (value >> 8); + p[1] = (uint8_t) (value & 0xFF); + } +} + + +static void write32(uint8_t* data, uint32_t size, uint32_t pos, uint32_t value, bool littleEndian) +{ + assert(pos <= size - 4); + + uint8_t* p = data + pos; + + if (littleEndian) { + p[0] = (uint8_t) (value & 0xFF); + p[1] = (uint8_t) ((value >> 8) & 0xFF); + p[2] = (uint8_t) ((value >> 16) & 0xFF); + p[3] = (uint8_t) ((value >> 24) & 0xFF); + } else { + p[0] = (uint8_t) ((value >> 24) & 0xFF); + p[1] = (uint8_t) ((value >> 16) & 0xFF); + p[2] = (uint8_t) ((value >> 8) & 0xFF); + p[3] = (uint8_t) (value & 0xFF); + } +} + + +// Returns 0 if the query_tag was not found. +static uint32_t find_exif_tag_in_ifd(const uint8_t* exif, uint32_t size, + uint32_t ifd_offset, + uint16_t query_tag, + bool littleEndian, + int recursion_depth) +{ + const int MAX_IFD_TABLE_RECURSION_DEPTH = 5; + + if (recursion_depth > MAX_IFD_TABLE_RECURSION_DEPTH) { + return 0; + } + + uint32_t offset = ifd_offset; + + // is offset valid (i.e. can we read at least the 'size' field and the pointer to the next IFD ?) + if (offset == 0) { + return 0; + } + + if (size < 6 || size - 2 - 4 < offset) { + return 0; + } + + uint16_t cnt = read16(exif, size, offset, littleEndian); + + // Does the IFD table fit into our memory range? We need this check to prevent an underflow in the following statement. + uint32_t IFD_table_size = 2U + cnt * 12U + 4U; + if (IFD_table_size > size) { + return 0; + } + + // end of IFD table would exceed the end of the EXIF data + // offset + IFD_table_size > size ? + if (size - IFD_table_size < offset) { + return 0; + } + + for (int i = 0; i < cnt; i++) { + int tag = read16(exif, size, offset + 2 + i * 12, littleEndian); + if (tag == query_tag) { + return offset + 2 + i * 12; + } + + if (tag == EXIF_TAG_EXIF_IFD_POINTER) { + uint32_t exifIFD_offset = read32(exif, size, offset + 2 + i * 12 + 8, littleEndian); + uint32_t tag_position = find_exif_tag_in_ifd(exif, size, exifIFD_offset, query_tag, littleEndian, + recursion_depth + 1); + if (tag_position) { + return tag_position; + } + } + } + + // continue with next IFD table + + uint32_t pos = offset + 2 + cnt * 12; + uint32_t next_ifd_offset = read32(exif, size, pos, littleEndian); + + return find_exif_tag_in_ifd(exif, size, next_ifd_offset, query_tag, littleEndian, recursion_depth + 1); +} + + +// Returns 0 if the query_tag was not found. +static uint32_t find_exif_tag(const uint8_t* exif, uint32_t size, uint16_t query_tag, bool* out_littleEndian) +{ + // read TIFF header + + if (size < 4) { + return 0; + } + + if ((exif[0] != 'I' && exif[0] != 'M') || + (exif[1] != 'I' && exif[1] != 'M')) { + return 0; + } + + bool littleEndian = (exif[0] == 'I'); + + assert(out_littleEndian); + *out_littleEndian = littleEndian; + + + // read main IFD table + + uint32_t offset; + offset = read32(exif, size, 4, littleEndian); + + uint32_t tag_position = find_exif_tag_in_ifd(exif, size, offset, query_tag, littleEndian, 1); + return tag_position; +} + + +void overwrite_exif_image_size_if_it_exists(uint8_t* exif, uint32_t size, uint32_t width, uint32_t height) +{ + bool little_endian; + uint32_t pos; + + for (uint16_t tag: {EXIF_TAG_IMAGE_WIDTH, EXIF_TAG_VALID_IMAGE_WIDTH}) { + pos = find_exif_tag(exif, size, tag, &little_endian); + if (pos != 0) { + write16(exif, size, pos + 2, EXIF_TYPE_LONG, little_endian); + write32(exif, size, pos + 4, 1, little_endian); + write32(exif, size, pos + 8, width, little_endian); + } + } + + for (uint16_t tag: {EXIF_TAG_IMAGE_HEIGHT, EXIF_TAG_VALID_IMAGE_HEIGHT}) { + pos = find_exif_tag(exif, size, tag, &little_endian); + if (pos != 0) { + write16(exif, size, pos + 2, EXIF_TYPE_LONG, little_endian); + write32(exif, size, pos + 4, 1, little_endian); + write32(exif, size, pos + 8, height, little_endian); + } + } +} + + +void modify_exif_tag_if_it_exists(uint8_t* exif, uint32_t size, uint16_t modify_tag, uint16_t modify_value) +{ + bool little_endian; + uint32_t pos = find_exif_tag(exif, size, modify_tag, &little_endian); + if (pos == 0) { + return; + } + + uint16_t type = read16(exif, size, pos + 2, little_endian); + uint32_t count = read32(exif, size, pos + 4, little_endian); + + if (type == EXIF_TYPE_SHORT && count == 1) { + write16(exif, size, pos + 8, modify_value, little_endian); + } +} + + +void modify_exif_orientation_tag_if_it_exists(uint8_t* exifData, uint32_t size, uint16_t orientation) +{ + modify_exif_tag_if_it_exists(exifData, size, EXIF_TAG_ORIENTATION, orientation); +} + + +int read_exif_orientation_tag(const uint8_t* exif, uint32_t size) +{ + bool little_endian; + uint32_t pos = find_exif_tag(exif, size, EXIF_TAG_ORIENTATION, &little_endian); + if (pos == 0) { + return DEFAULT_EXIF_ORIENTATION; + } + + uint16_t type = read16(exif, size, pos + 2, little_endian); + uint32_t count = read32(exif, size, pos + 4, little_endian); + + if (type == EXIF_TYPE_SHORT && count == 1) { + return read16(exif, size, pos + 8, little_endian); + } + + return DEFAULT_EXIF_ORIENTATION; +} diff --git a/heifio/exif.h b/heifio/exif.h new file mode 100644 index 0000000000..2a4de22e89 --- /dev/null +++ b/heifio/exif.h @@ -0,0 +1,33 @@ +/* + * HEIF codec. + * Copyright (c) 2022 Dirk Farin + * + * This file is part of libheif. + * + * libheif is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * libheif is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libheif. If not, see . + */ + +#ifndef LIBHEIF_EXIF_H +#define LIBHEIF_EXIF_H + +#include +#include + +int read_exif_orientation_tag(const uint8_t* exif, uint32_t size); + +void modify_exif_orientation_tag_if_it_exists(uint8_t* exifData, uint32_t size, uint16_t orientation); + +void overwrite_exif_image_size_if_it_exists(uint8_t* exif, uint32_t size, uint32_t width, uint32_t height); + +#endif //LIBHEIF_EXIF_H diff --git a/heifio/stubs.cc b/heifio/stubs.cc new file mode 100644 index 0000000000..b1facb614a --- /dev/null +++ b/heifio/stubs.cc @@ -0,0 +1,64 @@ +/* + libheif example application "heif". + + MIT License + + Copyright (c) 2024 Dirk Farin + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +#include "libheif/heif.h" +#include "decoder.h" + + +#if !HAVE_LIBJPEG +heif_error loadJPEG(const char *filename, InputImage *input_image) +{ + struct heif_error err = { + .code = heif_error_Unsupported_feature, + .subcode = heif_suberror_Unspecified, + .message = "Cannot load JPEG because libjpeg support was not compiled."}; + return err; +} +#endif + + +#if !HAVE_LIBPNG +heif_error loadPNG(const char* filename, int output_bit_depth, InputImage *input_image) +{ + struct heif_error err = { + .code = heif_error_Unsupported_feature, + .subcode = heif_suberror_Unspecified, + .message = "Cannot load PNG because libpng support was not compiled."}; + return err; +} +#endif + + +#if !HAVE_LIBTIFF +heif_error loadTIFF(const char *filename, InputImage *input_image) +{ + struct heif_error err = { + .code = heif_error_Unsupported_feature, + .subcode = heif_suberror_Unspecified, + .message = "Cannot load TIFF because libtiff support was not compiled."}; + return err; +} +#endif diff --git a/libheif.pc.in b/libheif.pc.in index e2a623c099..8a62403c88 100644 --- a/libheif.pc.in +++ b/libheif.pc.in @@ -2,17 +2,14 @@ prefix=@prefix@ exec_prefix=@exec_prefix@ libdir=@libdir@ includedir=@includedir@ -builtin_h265_decoder=@have_libde265@ -builtin_h265_encoder=@have_x265@ -builtin_avif_decoder=@have_avif_decoder@ -builtin_avif_encoder=@have_avif_encoder@ Name: libheif Description: HEIF image codec. URL: https://github.com/strukturag/libheif -Version: @VERSION@ +Version: @PROJECT_VERSION@ Requires: Requires.private: @REQUIRES_PRIVATE@ -Libs: -L@libdir@ -lheif -Libs.private: @LIBS@ -lstdc++ -Cflags: -I@includedir@ +Libs: -L${libdir} -lheif +Libs.private: @LIBS_PRIVATE@ +Cflags: -I${includedir} +Cflags.private: -DLIBHEIF_STATIC_BUILD -DHEIF_WITH_OMAF=@WITH_OMAF@ diff --git a/libheif/CMakeLists.txt b/libheif/CMakeLists.txt index 761271fe3f..e747083872 100644 --- a/libheif/CMakeLists.txt +++ b/libheif/CMakeLists.txt @@ -1,169 +1,371 @@ include(CMakePackageConfigHelpers) -configure_file(heif_version.h.in ${CMAKE_CURRENT_BINARY_DIR}/heif_version.h) +configure_file(api/libheif/heif_version.h.in ${CMAKE_CURRENT_BINARY_DIR}/heif_version.h) set(libheif_headers - heif.h - heif_cxx.h - heif_plugin.h - ${CMAKE_CURRENT_BINARY_DIR}/heif_version.h) - -add_library(heif - bitstream.cc - box.cc - error.cc - heif.cc - heif_context.cc - heif_file.cc - heif_image.cc - heif_hevc.cc - heif_avif.cc - heif_colorconversion.cc - heif_plugin_registry.cc - heif_plugin.cc - nclx.cc - bitstream.h - box.h - error.h - heif_api_structs.h - heif_context.h - heif_file.h - heif_image.h - heif_hevc.h - heif_avif.h - heif_colorconversion.h - heif_plugin_registry.h - heif_limits.h - nclx.h - logging.h - ${libheif_headers} -) + api/libheif/heif.h + api/libheif/heif_library.h + api/libheif/heif_image.h + api/libheif/heif_color.h + api/libheif/heif_error.h + api/libheif/heif_plugin.h + api/libheif/heif_properties.h + api/libheif/heif_regions.h + api/libheif/heif_items.h + api/libheif/heif_sequences.h + api/libheif/heif_tai_timestamps.h + api/libheif/heif_brands.h + api/libheif/heif_metadata.h + api/libheif/heif_aux_images.h + api/libheif/heif_entity_groups.h + api/libheif/heif_security.h + api/libheif/heif_encoding.h + api/libheif/heif_decoding.h + api/libheif/heif_image_handle.h + api/libheif/heif_context.h + api/libheif/heif_tiling.h + api/libheif/heif_uncompressed.h + api/libheif/heif_uncompressed_types.h + api/libheif/heif_text.h + api/libheif/heif_cxx.h + ${CMAKE_CURRENT_BINARY_DIR}/heif_version.h) + +set(libheif_sources + bitstream.cc + bitstream.h + box.cc + box.h + error.cc + error.h + context.cc + context.h + file.cc + file.h + file_layout.h + file_layout.cc + pixelimage.cc + pixelimage.h + plugin_registry.cc + nclx.cc + nclx.h + plugin_registry.h + security_limits.cc + security_limits.h + init.cc + init.h + logging.h + logging.cc + compression.h + compression.cc + compression_brotli.cc + compression_zlib.cc + common_utils.cc + common_utils.h + region.cc + region.h + brands.cc + brands.h + id_creator.cc + id_creator.h + text.cc + text.h + api_structs.h + api/libheif/heif.cc + api/libheif/heif_library.cc + api/libheif/heif_image.cc + api/libheif/heif_color.cc + api/libheif/heif_regions.cc + api/libheif/heif_plugin.cc + api/libheif/heif_properties.cc + api/libheif/heif_items.cc + api/libheif/heif_sequences.cc + api/libheif/heif_tai_timestamps.cc + api/libheif/heif_brands.cc + api/libheif/heif_metadata.cc + api/libheif/heif_aux_images.cc + api/libheif/heif_entity_groups.cc + api/libheif/heif_security.cc + api/libheif/heif_encoding.cc + api/libheif/heif_decoding.cc + api/libheif/heif_image_handle.cc + api/libheif/heif_context.cc + api/libheif/heif_tiling.cc + api/libheif/heif_uncompressed.cc + api/libheif/heif_text.cc + codecs/decoder.h + codecs/decoder.cc + codecs/encoder.h + codecs/encoder.cc + image-items/hevc.cc + image-items/hevc.h + codecs/hevc_boxes.cc + codecs/hevc_boxes.h + codecs/hevc_dec.cc + codecs/hevc_dec.h + codecs/hevc_enc.cc + codecs/hevc_enc.h + image-items/avif.cc + image-items/avif.h + codecs/avif_enc.cc + codecs/avif_enc.h + codecs/avif_dec.cc + codecs/avif_dec.h + codecs/avif_boxes.cc + codecs/avif_boxes.h + image-items/jpeg.h + image-items/jpeg.cc + codecs/jpeg_boxes.h + codecs/jpeg_boxes.cc + codecs/jpeg_dec.h + codecs/jpeg_dec.cc + codecs/jpeg_enc.h + codecs/jpeg_enc.cc + image-items/jpeg2000.h + image-items/jpeg2000.cc + codecs/jpeg2000_dec.h + codecs/jpeg2000_dec.cc + codecs/jpeg2000_enc.h + codecs/jpeg2000_enc.cc + codecs/jpeg2000_boxes.h + codecs/jpeg2000_boxes.cc + image-items/vvc.h + image-items/vvc.cc + codecs/vvc_dec.h + codecs/vvc_dec.cc + codecs/vvc_enc.h + codecs/vvc_enc.cc + codecs/vvc_boxes.h + codecs/vvc_boxes.cc + image-items/avc.h + image-items/avc.cc + codecs/avc_boxes.h + codecs/avc_boxes.cc + codecs/avc_dec.h + codecs/avc_dec.cc + codecs/avc_enc.h + codecs/avc_enc.cc + image-items/mask_image.h + image-items/mask_image.cc + image-items/image_item.h + image-items/image_item.cc + image-items/grid.h + image-items/grid.cc + image-items/overlay.h + image-items/overlay.cc + image-items/iden.h + image-items/iden.cc + image-items/tiled.h + image-items/tiled.cc + color-conversion/colorconversion.cc + color-conversion/colorconversion.h + color-conversion/rgb2yuv.cc + color-conversion/rgb2yuv.h + color-conversion/rgb2yuv_sharp.cc + color-conversion/rgb2yuv_sharp.h + color-conversion/yuv2rgb.cc + color-conversion/yuv2rgb.h + color-conversion/rgb2rgb.cc + color-conversion/rgb2rgb.h + color-conversion/monochrome.cc + color-conversion/monochrome.h + color-conversion/hdr_sdr.cc + color-conversion/hdr_sdr.h + color-conversion/alpha.cc + color-conversion/alpha.h + color-conversion/chroma_sampling.cc + color-conversion/chroma_sampling.h + color-conversion/bayer_bilinear.cc + color-conversion/bayer_bilinear.h + sequences/seq_boxes.h + sequences/seq_boxes.cc + sequences/chunk.h + sequences/chunk.cc + sequences/track.h + sequences/track.cc + sequences/track_visual.h + sequences/track_visual.cc + sequences/track_metadata.h + sequences/track_metadata.cc + ${libheif_headers}) + +add_library(heif ${libheif_sources}) + +if (ENABLE_PLUGIN_LOADING) + if (WIN32) + target_sources(heif PRIVATE plugins_windows.cc plugins_windows.h) + else () + target_sources(heif PRIVATE plugins_unix.cc plugins_unix.h) + endif () +endif () + +option(ENABLE_EXPERIMENTAL_FEATURES "Compile experimental features and install headers with unstable API" OFF) +if (ENABLE_EXPERIMENTAL_FEATURES) + target_sources(heif PRIVATE api/libheif/heif_experimental.h api/libheif/heif_experimental.cc) + + list(APPEND libheif_headers api/libheif/heif_experimental.h) + + target_compile_definitions(heif PUBLIC HEIF_ENABLE_EXPERIMENTAL_FEATURES) +endif() # Needed to find libheif/heif_version.h while compiling the library -target_include_directories(heif PRIVATE ${libheif_BINARY_DIR} ${libheif_SOURCE_DIR}) +target_include_directories(heif PRIVATE ${libheif_BINARY_DIR} ${libheif_SOURCE_DIR}/libheif ${libheif_SOURCE_DIR}/libheif/api) # Propagate include/libheif to consume the headers from other projects target_include_directories(heif - PUBLIC - $ - $ - $) + PUBLIC + $ + $ + $) set_target_properties(heif - PROPERTIES - VERSION ${PROJECT_VERSION} - SOVERSION ${PROJECT_VERSION_MAJOR}) + PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION ${PROJECT_VERSION_MAJOR}) -target_compile_definitions(heif - PUBLIC - LIBHEIF_EXPORTS - HAVE_VISIBILITY) - -if(LIBDE265_FOUND) - target_compile_definitions(heif PRIVATE HAVE_LIBDE265=1) - target_sources(heif - PRIVATE - heif_decoder_libde265.cc - heif_decoder_libde265.h) +if (APPLE) + set_target_properties(heif PROPERTIES + LINK_FLAGS "-Wl,-compatibility_version,${MACOS_COMPATIBLE_VERSION}") +endif () - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${LIBDE265_CFLAGS}") +if (BUILD_FRAMEWORK) + set_target_properties(heif PROPERTIES + FRAMEWORK TRUE + FRAMEWORK_VERSION "${PACKAGE_VERSION}" + PRODUCT_BUNDLE_IDENTIFIER "github.com/strukturag/libheif" + XCODE_ATTRIBUTE_INSTALL_PATH "@rpath" + # OUTPUT_NAME "heif" + XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "" + XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO" + XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO" + PUBLIC_HEADER "${libheif_headers}" + MACOSX_FRAMEWORK_IDENTIFIER "github.com/strukturag/libheif" + MACOSX_FRAMEWORK_BUNDLE_VERSION "${PACKAGE_VERSION}" + MACOSX_FRAMEWORK_SHORT_VERSION_STRING "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}" + MACOSX_RPATH TRUE) +endif() - if (NOT "${LIBDE265_LIBRARY_DIRS}" STREQUAL "") - set(LIBDE265_LINKDIR "-L${LIBDE265_LIBRARY_DIRS}") - endif() +target_compile_definitions(heif + PUBLIC + LIBHEIF_EXPORTS + HAVE_VISIBILITY) - include_directories(SYSTEM ${LIBDE265_INCLUDE_DIR}) - target_link_libraries(heif PRIVATE ${LIBDE265_LIBRARIES} ${LIBDE265_LINKDIR}) -endif() +if (PLUGIN_LOADING_SUPPORTED_AND_ENABLED) + target_compile_definitions(heif PRIVATE ENABLE_PLUGIN_LOADING=1) + target_link_libraries(heif PRIVATE ${CMAKE_DL_LIBS}) +endif () -if(X265_FOUND) - target_compile_definitions(heif PRIVATE HAVE_X265=1) - target_sources(heif PRIVATE - heif_encoder_x265.cc - heif_encoder_x265.h - ) +add_subdirectory(plugins) - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${X265_CFLAGS}") +if (LIBSHARPYUV_FOUND) + message("Compiling in 'libsharpyuv'") + target_compile_definitions(heif PUBLIC HAVE_LIBSHARPYUV=1) + target_include_directories(heif PRIVATE ${LIBSHARPYUV_INCLUDE_DIRS}) + target_link_libraries(heif PRIVATE ${LIBSHARPYUV_LIBRARIES}) +else () + message("Not compiling 'libsharpyuv'") +endif () - if (NOT "${X265_LIBRARY_DIRS}" STREQUAL "") - set(X265_LINKDIR "-L${X265_LIBRARY_DIRS}") - endif() +if (ZLIB_FOUND) + target_compile_definitions(heif PRIVATE HAVE_ZLIB=1) + target_link_libraries(heif PRIVATE ZLIB::ZLIB) +endif () - include_directories(SYSTEM ${X265_INCLUDE_DIR}) - target_link_libraries(heif PRIVATE ${X265_LIBRARIES} ${X265_LINKDIR}) +if (Brotli_FOUND) + target_compile_definitions(heif PUBLIC HAVE_BROTLI=1) + target_include_directories(heif PRIVATE ${BROTLI_INCLUDE_DIRS}) + target_link_libraries(heif PRIVATE ${BROTLI_LIBS}) endif() -if(AOM_ENCODER_FOUND OR AOM_DECODER_FOUND) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${AOM_CFLAGS}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${AOM_CFLAGS}") +if (ENABLE_MULTITHREADING_SUPPORT) + find_package(Threads) + target_link_libraries(heif PRIVATE ${CMAKE_THREAD_LIBS_INIT}) - if (NOT "${AOM_LIBRARY_DIRS}" STREQUAL "") - set(AOM_LINKDIR "-L${AOM_LIBRARY_DIRS}") - endif() + target_compile_definitions(heif PRIVATE ENABLE_MULTITHREADING_SUPPORT=1) + if (ENABLE_PARALLEL_TILE_DECODING) + target_compile_definitions(heif PRIVATE ENABLE_PARALLEL_TILE_DECODING=1) + endif () +endif () - include_directories(SYSTEM ${AOM_INCLUDE_DIR}) - target_link_libraries(heif PRIVATE ${AOM_LIBRARIES} ${AOM_LINKDIR}) -endif() -if(AOM_ENCODER_FOUND) - target_compile_definitions(heif PRIVATE HAVE_AOM_ENCODER=1) - target_sources(heif PRIVATE - heif_encoder_aom.cc - heif_encoder_aom.h - ) -endif() -if(AOM_DECODER_FOUND) - target_compile_definitions(heif PRIVATE HAVE_AOM_DECODER=1) - target_sources(heif PRIVATE - heif_decoder_aom.cc - heif_decoder_aom.h - ) -endif() +if (WITH_UNCOMPRESSED_CODEC) + target_compile_definitions(heif PUBLIC WITH_UNCOMPRESSED_CODEC=1) + target_sources(heif PRIVATE + codecs/uncompressed/unc_boxes.h + codecs/uncompressed/unc_boxes.cc + image-items/unc_image.h + image-items/unc_image.cc + codecs/uncompressed/unc_codec.h + codecs/uncompressed/unc_codec.cc + codecs/uncompressed/unc_dec.h + codecs/uncompressed/unc_dec.cc + codecs/uncompressed/unc_enc.h + codecs/uncompressed/unc_enc.cc + codecs/uncompressed/unc_decoder.h + codecs/uncompressed/unc_decoder.cc + codecs/uncompressed/unc_decoder_legacybase.h + codecs/uncompressed/unc_decoder_legacybase.cc + codecs/uncompressed/unc_decoder_component_interleave.h + codecs/uncompressed/unc_decoder_component_interleave.cc + codecs/uncompressed/unc_decoder_pixel_interleave.h + codecs/uncompressed/unc_decoder_pixel_interleave.cc + codecs/uncompressed/unc_decoder_block_pixel_interleave.h + codecs/uncompressed/unc_decoder_block_pixel_interleave.cc + codecs/uncompressed/unc_decoder_bytealign_component_interleave.h + codecs/uncompressed/unc_decoder_bytealign_component_interleave.cc + codecs/uncompressed/unc_decoder_block_component_interleave.h + codecs/uncompressed/unc_decoder_block_component_interleave.cc + codecs/uncompressed/unc_decoder_mixed_interleave.h + codecs/uncompressed/unc_decoder_mixed_interleave.cc + codecs/uncompressed/unc_decoder_row_interleave.h + codecs/uncompressed/unc_decoder_row_interleave.cc + codecs/uncompressed/unc_encoder.h + codecs/uncompressed/unc_encoder.cc + codecs/uncompressed/unc_encoder_rgb_pixel_interleave.cc + codecs/uncompressed/unc_encoder_rgb_pixel_interleave.h + codecs/uncompressed/unc_encoder_rgb_bytealign_pixel_interleave.cc + codecs/uncompressed/unc_encoder_rgb_bytealign_pixel_interleave.h + codecs/uncompressed/unc_encoder_component_interleave.cc + codecs/uncompressed/unc_encoder_component_interleave.h + codecs/uncompressed/unc_encoder_rgb_block_pixel_interleave.cc + codecs/uncompressed/unc_encoder_rgb_block_pixel_interleave.h) +endif () -if(RAV1E_FOUND) - target_compile_definitions(heif PRIVATE HAVE_RAV1E=1) +if (ENABLE_EXPERIMENTAL_MINI_FORMAT) + target_compile_definitions(heif PUBLIC ENABLE_EXPERIMENTAL_MINI_FORMAT=1) target_sources(heif PRIVATE - heif_encoder_rav1e.cc - heif_encoder_rav1e.h - ) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${RAV1E_CFLAGS}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${RAV1E_CFLAGS}") - - if (NOT "${RAV1E_LIBRARY_DIRS}" STREQUAL "") - set(RAV1E_LINKDIR "-L${RAV1E_LIBRARY_DIRS}") - endif() - - include_directories(SYSTEM ${RAV1E_INCLUDE_DIR}) - target_link_libraries(heif PRIVATE ${RAV1E_LIBRARIES} ${RAV1E_LINKDIR}) -endif() + mini.h + mini.cc) +endif () -if(DAV1D_FOUND) - target_compile_definitions(heif PRIVATE HAVE_DAV1D=1) +if (HEIF_WITH_OMAF) + target_compile_definitions(heif PUBLIC HEIF_WITH_OMAF=1) target_sources(heif PRIVATE - heif_decoder_dav1d.cc - heif_decoder_dav1d.h - ) - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${DAV1D_CFLAGS}") - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${DAV1D_CFLAGS}") - - if (NOT "${DAV1D_LIBRARY_DIRS}" STREQUAL "") - set(DAV1D_LINKDIR "-L${DAV1D_LIBRARY_DIRS}") - endif() - - include_directories(SYSTEM ${DAV1D_INCLUDE_DIR}) - target_link_libraries(heif PRIVATE ${DAV1D_LIBRARIES} ${DAV1D_LINKDIR}) + omaf_boxes.h + omaf_boxes.cc) endif () write_basic_package_version_file(${PROJECT_NAME}-config-version.cmake COMPATIBILITY ExactVersion) install(TARGETS heif EXPORT ${PROJECT_NAME}-config - RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} - LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} - ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} -) + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + FRAMEWORK DESTINATION Library/Frameworks COMPONENT runtime OPTIONAL + ) install(FILES ${libheif_headers} DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${PROJECT_NAME}) install(EXPORT ${PROJECT_NAME}-config DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake DESTINATION - "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") + "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") + + +# --- on Windows, copy the DLL into the executable directory for easier development + +if (WIN32 AND BUILD_SHARED_LIBS) + add_custom_command(TARGET heif POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + $ + $/../examples + ) +endif () diff --git a/libheif/CPPLINT.cfg b/libheif/CPPLINT.cfg deleted file mode 100644 index 732f723d2c..0000000000 --- a/libheif/CPPLINT.cfg +++ /dev/null @@ -1 +0,0 @@ -# filter=-build/include_what_you_use diff --git a/libheif/Doxyfile.in b/libheif/Doxyfile.in new file mode 100644 index 0000000000..0d6c130852 --- /dev/null +++ b/libheif/Doxyfile.in @@ -0,0 +1,2562 @@ +# Doxyfile 1.9.1 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the configuration +# file that follow. The default is UTF-8 which is also the encoding used for all +# text before the first occurrence of this tag. Doxygen uses libiconv (or the +# iconv built into libc) for the transcoding. See +# https://www.gnu.org/software/libiconv/ for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "libheif" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = @CMAKE_CURRENT_BINARY_DIR@/apidoc/ + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = @CMAKE_CURRENT_SOURCE_DIR@ + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = YES + +# If the JAVADOC_BANNER tag is set to YES then doxygen will interpret a line +# such as +# /*************** +# as being the beginning of a Javadoc-style comment "banner". If set to NO, the +# Javadoc-style will behave just like regular comments and it will not be +# interpreted by doxygen. +# The default value is: NO. + +JAVADOC_BANNER = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# By default Python docstrings are displayed as preformatted text and doxygen's +# special commands cannot be used. By setting PYTHON_DOCSTRING to NO the +# doxygen's special commands can be used and the contents of the docstring +# documentation blocks is shown as doxygen documentation. +# The default value is: YES. + +PYTHON_DOCSTRING = YES + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines (in the resulting output). You can put ^^ in the value part of an +# alias to insert a newline as if a physical newline was in the original file. +# When you need a literal { or } or , in the value part of an alias you have to +# escape them by means of a backslash (\), this can lead to conflicts with the +# commands \{ and \} for these it is advised to use the version @{ and @} or use +# a double escape (\\{ and \\}) + +ALIASES = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = YES + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Set the OPTIMIZE_OUTPUT_SLICE tag to YES if your project consists of Slice +# sources only. Doxygen will then generate output that is more tailored for that +# language. For instance, namespaces will be presented as modules, types will be +# separated into more groups, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_SLICE = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, JavaScript, +# Csharp (C#), C, C++, D, PHP, md (Markdown), Objective-C, Python, Slice, VHDL, +# Fortran (fixed format Fortran: FortranFixed, free formatted Fortran: +# FortranFree, unknown formatted Fortran: Fortran. In the later case the parser +# tries to guess whether the code is fixed or free formatted code, this is the +# default for Fortran type files). For instance to make doxygen treat .inc files +# as Fortran files (default is PHP), and .f files as C (default is Fortran), +# use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. When specifying no_extension you should add +# * to the FILE_PATTERNS. +# +# Note see also the list of default file extension mappings. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See https://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When the TOC_INCLUDE_HEADINGS tag is set to a non-zero value, all headings up +# to that level are automatically included in the table of contents, even if +# they do not have an id attribute. +# Note: This feature currently applies only to Markdown headings. +# Minimum value: 0, maximum value: 99, default value: 5. +# This tag requires that the tag MARKDOWN_SUPPORT is set to YES. + +TOC_INCLUDE_HEADINGS = 5 + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# https://www.riverbankcomputing.com/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +# The NUM_PROC_THREADS specifies the number threads doxygen is allowed to use +# during processing. When set to 0 doxygen will based this on the number of +# cores available in the system. You can set it explicitly to a value larger +# than 0 to get more control over the balance between CPU load and processing +# speed. At this moment only the input processing can be done using multiple +# threads. Since this is still an experimental feature the default is set to 1, +# which efficively disables parallel processing. Please report any issues you +# encounter. Generating dot graphs in parallel is controlled by the +# DOT_NUM_THREADS setting. +# Minimum value: 0, maximum value: 32, default value: 1. + +NUM_PROC_THREADS = 1 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = YES + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PRIV_VIRTUAL tag is set to YES, documented private virtual +# methods of a class will be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIV_VIRTUAL = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If this flag is set to YES, the name of an unnamed parameter in a declaration +# will be determined by the corresponding definition. By default unnamed +# parameters remain unnamed in the output. +# The default value is: YES. + +RESOLVE_UNNAMED_PARAMS = YES + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# declarations. If set to NO, these declarations will be included in the +# documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# With the correct setting of option CASE_SENSE_NAMES doxygen will better be +# able to match the capabilities of the underlying filesystem. In case the +# filesystem is case sensitive (i.e. it supports files in the same directory +# whose names only differ in casing), the option must be set to YES to properly +# deal with such files in case they appear in the input. For filesystems that +# are not case sensitive the option should be be set to NO to properly deal with +# output files written for symbols that only differ in casing, such as for two +# classes, one named CLASS and the other named Class, and to also support +# references to files without having to specify the exact matching casing. On +# Windows (including Cygwin) and MacOS, users should typically set this option +# to NO, whereas on Linux or other Unix flavors it should typically be set to +# YES. +# The default value is: system dependent. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE= NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST= YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also https://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = YES + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = YES + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = YES + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = YES + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. If +# EXTRACT_ALL is set to YES then this flag will automatically be disabled. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. If the WARN_AS_ERROR tag is set to FAIL_ON_WARNINGS +# then doxygen will continue running as if WARN_AS_ERROR tag is set to NO, but +# at the end of the doxygen process doxygen will return with a non-zero status. +# Possible values are: NO, YES and FAIL_ON_WARNINGS. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = @CMAKE_CURRENT_SOURCE_DIR@/libheif/api/libheif/heif.h \ +@CMAKE_CURRENT_SOURCE_DIR@/libheif/api/libheif/heif_items.h \ +@CMAKE_CURRENT_SOURCE_DIR@/libheif/api/libheif/heif_regions.h \ +@CMAKE_CURRENT_SOURCE_DIR@/libheif/api/libheif/heif_text.h +@CMAKE_CURRENT_SOURCE_DIR@/libheif/api/libheif/heif_library.h \ +@CMAKE_CURRENT_SOURCE_DIR@/libheif/api/libheif/heif_encoding.h \ +@CMAKE_CURRENT_SOURCE_DIR@/libheif/api/libheif/heif_sequences.h + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: +# https://www.gnu.org/software/libiconv/) for the list of possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# Note the list of default checked file patterns might differ from the list of +# default file extension mappings. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox (to be provided as doxygen C comment), +# *.py, *.pyw, *.f90, *.f95, *.f03, *.f08, *.f18, *.f, *.for, *.vhd, *.vhdl, +# *.ucf, *.qsf and *.ice. + +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f18 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = NO + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = * + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# entity all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see https://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the configuration file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: +# http://clang.llvm.org/) for more accurate parsing at the cost of reduced +# performance. This can be particularly helpful with template rich C++ code for +# which doxygen's built-in parser lacks the necessary type information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. +# The default value is: NO. + +CLANG_ASSISTED_PARSING = NO + +# If clang assisted parsing is enabled and the CLANG_ADD_INC_PATHS tag is set to +# YES then doxygen will add the directory of each input to the include path. +# The default value is: YES. + +CLANG_ADD_INC_PATHS = YES + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +CLANG_OPTIONS = + +# If clang assisted parsing is enabled you can provide the clang parser with the +# path to the directory containing a file called compile_commands.json. This +# file is the compilation database (see: +# http://clang.llvm.org/docs/HowToSetupToolingForLLVM.html) containing the +# options used when the source files were built. This is equivalent to +# specifying the -p option to a clang tool, such as clang-check. These options +# will then be passed to the parser. Any options specified with CLANG_OPTIONS +# will be added as well. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse_libclang=ON option for CMake. + +CLANG_DATABASE_PATH = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = html + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# https://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML +# documentation will contain a main index with vertical navigation menus that +# are dynamically created via JavaScript. If disabled, the navigation index will +# consists of multiple levels of tabs that are statically embedded in every HTML +# page. Disable this option to support browsers that do not have JavaScript, +# like the Qt help browser. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_MENUS = YES + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: +# https://developer.apple.com/xcode/), introduced with OSX 10.5 (Leopard). To +# create a documentation set, doxygen will generate a Makefile in the HTML +# output directory. Running make will produce the docset in that directory and +# running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See https://developer.apple.com/library/archive/featuredarticles/Doxy +# genXcode/_index.html for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: +# https://www.microsoft.com/en-us/download/details.aspx?id=21138) on Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the main .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#virtual-folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#custom-filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# https://doc.qt.io/archives/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location (absolute path +# including file name) of Qt's qhelpgenerator. If non-empty doxygen will try to +# run qhelpgenerator on the generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = NO + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# If the HTML_FORMULA_FORMAT option is set to svg, doxygen will use the pdf2svg +# tool (see https://github.com/dawbarton/pdf2svg) or inkscape (see +# https://inkscape.org) to generate formulas as SVG images instead of PNGs for +# the HTML output. These images will generally look nicer at scaled resolutions. +# Possible values are: png (the default) and svg (looks nicer but requires the +# pdf2svg or inkscape tool). +# The default value is: png. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FORMULA_FORMAT = png + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands +# to create new LaTeX commands to be used in formulas as building blocks. See +# the section "Including formulas" for details. + +FORMULA_MACROFILE = + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# https://www.mathjax.org) which uses client side JavaScript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from https://www.mathjax.org before deployment. +# The default value is: https://cdn.jsdelivr.net/npm/mathjax@2. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = https://cdn.jsdelivr.net/npm/mathjax@2 + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: +# http://docs.mathjax.org/en/v2.7-latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /