diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml index fd5ab0b603..d7b7eb3414 100644 --- a/.github/workflows/coverity.yml +++ b/.github/workflows/coverity.yml @@ -2,7 +2,7 @@ name: coverity on: push: - branches: [ coverity ] + branches: [ master, coverity ] jobs: scan: @@ -10,8 +10,10 @@ jobs: env: TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} WITH_AOM: 1 + WITH_DAV1D: 1 WITH_GRAPHICS: 1 WITH_LIBDE265: 1 + WITH_RAV1E: 1 WITH_X265: 1 steps: - uses: actions/checkout@v4 @@ -21,7 +23,7 @@ jobs: 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/emscripten.yml b/.github/workflows/emscripten.yml index 0b36c40a00..157a7cbba3 100644 --- a/.github/workflows/emscripten.yml +++ b/.github/workflows/emscripten.yml @@ -9,7 +9,7 @@ on: jobs: emscripten: env: - EMSCRIPTEN_VERSION: 3.1.47 + EMSCRIPTEN_VERSION: 3.1.61 runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v4 diff --git a/.github/workflows/osx.yml b/.github/workflows/osx.yml index 43e228e965..d045a79705 100644 --- a/.github/workflows/osx.yml +++ b/.github/workflows/osx.yml @@ -10,7 +10,7 @@ jobs: build: strategy: matrix: - runner: [ macos-12, macos-14 ] + runner: [ macos-13, macos-15 ] env: - { NAME: "nothing" } - { NAME: "cmake", WITH_GRAPHICS: 1, WITH_X265: 1, WITH_AOM: 1, WITH_LIBDE265: 1 } diff --git a/CMakeLists.txt b/CMakeLists.txt index 3f594381d5..7f3d0bc87b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required (VERSION 3.16.3) # Oldest Ubuntu LTS (20.04 currently) -project(libheif LANGUAGES C CXX VERSION 1.18.0) +project(libheif LANGUAGES C CXX VERSION 1.18.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. @@ -25,10 +25,15 @@ if (HAVE_UNISTD_H) add_definitions(-DHAVE_UNISTD_H) endif() +set(CMAKE_COMPILE_WARNING_AS_ERROR ON CACHE BOOL "Treat compilation warning as error") if(NOT MSVC) + # cmake 3.24 introduces this variable, but for backward compatibility, we set it explicitly + if (CMAKE_COMPILE_WARNING_AS_ERROR) + add_definitions(-Werror) + endif () + add_definitions(-Wall) - add_definitions(-Werror) add_definitions(-Wsign-compare) add_definitions(-Wconversion) add_definitions(-Wno-sign-conversion) @@ -41,7 +46,7 @@ if(NOT MSVC) endif () endif() -set(CMAKE_CXX_STANDARD 11) +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_CXX_EXTENSIONS OFF) @@ -70,11 +75,19 @@ add_compile_definitions(IS_BIG_ENDIAN=${IS_BIG_ENDIAN}) # --- codec plugins option(ENABLE_PLUGIN_LOADING "Support loading of plugins" ON) -set(PLUGIN_DIRECTORY "${CMAKE_INSTALL_FULL_LIBDIR}/libheif" CACHE STRING "Plugin install directory") +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) - install(DIRECTORY DESTINATION ${PLUGIN_DIRECTORY} DIRECTORY_PERMISSIONS + + 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) @@ -153,6 +166,37 @@ if (WITH_VVDEC) endif() endif () +# vvenc + +plugin_option(VVENC "vvenc VVC encoder (experimental)" 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 () + +# 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) @@ -240,11 +284,14 @@ 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(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 enc. (experimental)") +plugin_compilation_info(VVENC vvenc "vvenc VVC enc. (experimental)") plugin_compilation_info(VVDEC vvdec "vvdec VVC dec. (experimental)") # --- show summary which formats are supported @@ -301,9 +348,18 @@ endif() if (OPENJPH_FOUND AND WITH_OPENJPH_DECODER) set(SUPPORTS_J2K_HT_ENCODING TRUE) endif() -if (UVG266_FOUND) +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 (OpenH264_ENCODER_FOUND) + set(SUPPORTS_AVC_ENCODING TRUE) +endif() if (WITH_UNCOMPRESSED_CODEC) set(SUPPORTS_UNCOMPRESSED_DECODING TRUE) @@ -312,13 +368,15 @@ endif() message("\n=== Supported formats ===") message("format decoding encoding") -format_compilation_info("HEIC" SUPPORTS_HEIC_DECODING SUPPORTS_HEIC_ENCODING) +format_compilation_info("AVC" SUPPORTS_AVC_DECODING SUPPORTS_AVC_ENCODING) format_compilation_info("AVIF" SUPPORTS_AVIF_DECODING SUPPORTS_AVIF_ENCODING) -format_compilation_info("VVC" FALSE SUPPORTS_VVC_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 @@ -383,8 +441,22 @@ endif() if (LIBSHARPYUV_FOUND) list(APPEND REQUIRES_PRIVATE "libsharpyuv") endif() -if (WITH_DEFLATE_HEADER_COMPRESSION) - list(APPEND REQUIRES_PRIVATE "zlib") +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() list(JOIN REQUIRES_PRIVATE " " REQUIRES_PRIVATE) @@ -409,7 +481,7 @@ option(WITH_GDK_PIXBUF "Build gdk-pixbuf plugin" ON) option(WITH_REDUCED_VISIBILITY "Reduced symbol visibility in library" ON) -option(WITH_DEFLATE_HEADER_COMPRESSION OFF) +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) @@ -419,6 +491,8 @@ else () set(CMAKE_CXX_VISIBILITY_PRESET default) endif () +add_subdirectory(heifio) + if(WITH_EXAMPLES) add_subdirectory (examples) endif() diff --git a/CMakePresets.json b/CMakePresets.json index 845adf4eec..4acbe1184b 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -14,6 +14,7 @@ "CMAKE_BUILD_TYPE": "Debug", "BUILD_SHARED_LIBS": "ON", "BUILD_TESTING" : "ON", + "WITH_EXPERIMENTAL_FEATURES" : "ON", "ENABLE_PLUGIN_LOADING" : "OFF", "WITH_AOM_DECODER" : "ON", @@ -45,7 +46,7 @@ "WITH_FFMPEG_DECODER_PLUGIN" : "OFF", "WITH_REDUCED_VISIBILITY" : "OFF", - "WITH_DEFLATE_HEADER_COMPRESSION" : "ON", + "WITH_HEADER_COMPRESSION" : "ON", "WITH_LIBSHARPYUV" : "ON", "WITH_EXAMPLES": "ON", "WITH_FUZZERS": "OFF" @@ -91,7 +92,7 @@ "WITH_FFMPEG_DECODER_PLUGIN" : "ON", "WITH_REDUCED_VISIBILITY" : "ON", - "WITH_DEFLATE_HEADER_COMPRESSION" : "ON", + "WITH_HEADER_COMPRESSION" : "ON", "WITH_LIBSHARPYUV" : "ON", "WITH_EXAMPLES": "ON", "WITH_FUZZERS": "OFF" @@ -123,7 +124,7 @@ "WITH_FFMPEG_DECODER" : "OFF", "WITH_REDUCED_VISIBILITY" : "ON", - "WITH_DEFLATE_HEADER_COMPRESSION" : "OFF", + "WITH_HEADER_COMPRESSION" : "OFF", "WITH_LIBSHARPYUV" : "ON", "WITH_EXAMPLES": "ON", "WITH_FUZZERS": "OFF" diff --git a/README.md b/README.md index e9064f7931..dc2a4ddfe4 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,10 @@ For AVIF, libaom, dav1d, svt-av1, or rav1e are used as codecs. libheif has support for: -* HEIC, AVIF, JPEG-in-HEIF, JPEG2000, uncompressed (ISO/IEC 23001-17:2023) +* 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 +* 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 @@ -26,22 +27,23 @@ libheif has support for: * decoding of files while downloading (e.g. extract image size before file has been completely downloaded) Supported codecs: -| Format | Decoders | Encoders | -|:-------------|:-------------------:|:---------------------:| -| HEIC | libde265, ffmpeg | x265, kvazaar | -| AVIF | AOM, dav1d | AOM, rav1e, svt-av1 | -| VVC | vvdec (experimental)| uvg266 (experimental) | -| JPEG | libjpeg(-turbo) | libjpeg(-turbo) | -| JPEG2000 | OpenJPEG | OpenJPEG | -| uncompressed | built-in | built-in | +| Format | Decoders | Encoders | +|:-------------|:-------------------:|:----------------------------:| +| HEIC | libde265, ffmpeg | x265, kvazaar | +| AVIF | AOM, dav1d | AOM, rav1e, svt-av1 | +| VVC | vvdec | vvenc, uvg266 | +| AVC | openh264 | - | +| JPEG | libjpeg(-turbo) | libjpeg(-turbo) | +| JPEG2000 | OpenJPEG | OpenJPEG | +| uncompressed | built-in | built-in | ## API The library has a C API for easy integration and wide language support. -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 code 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: @@ -112,6 +114,13 @@ 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. +### Reading and Writing 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 [this tutorial](https://github.com/strukturag/libheif/wiki/Reading-and-Writing-Tiled-Images) on how to use the API for this. + + ## Compiling This library uses the CMake build system (the earlier autotools build files have been removed in v1.16.0). @@ -155,22 +164,25 @@ For each codec, there are two configuration variables: * `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`, `FFMPEG_DECODER`, `JPEG_DECODER`, `JPEG_ENCODER`, `KVAZAAR`, `OpenJPEG_DECODER`, `OpenJPEG_ENCODER`, `OPENJPH_ENCODER`, `UVG266`, `VVDEC`. +The placeholder `{codec}` can have these values: `LIBDE265`, `X265`, `AOM_DECODER`, `AOM_ENCODER`, `SvtEnc`, `DAV1D`, `FFMPEG_DECODER`, `JPEG_DECODER`, `JPEG_ENCODER`, `KVAZAAR`, `OpenJPEG_DECODER`, `OpenJPEG_ENCODER`, `OPENJPH_ENCODER`, `VVDEC`, `VVENC`, `UVG266`. Further options are: -* `WITH_UNCOMPRESSED_CODEC`: enable support for uncompressed images according to ISO/IEC 23001-17:2023. This is *experimental* - and not available as a dynamic plugin. -* `WITH_DEFLATE_HEADER_COMPRESSION`: enables support for compressed metadata. When enabled, it adds a dependency to `zlib`. +* `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). +* `WITH_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 the tests. + Has to be turned off for running some tests. ### macOS @@ -347,6 +359,7 @@ to update the gdk-pixbuf loader database. * [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) @@ -377,5 +390,5 @@ 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-2023 Dirk Farin
+Copyright (c) 2017-2024 Dirk Farin
Contact: Dirk Farin diff --git a/appveyor.yml b/appveyor.yml index f1922f9b81..031fa958d1 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -4,19 +4,21 @@ 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 - arch: arm64 install: - - vcpkg install libde265:%arch%-windows - - vcpkg install x265:%arch%-windows + - vcpkg install aom:%arch%-windows - vcpkg install dav1d:%arch%-windows + - vcpkg install ffmpeg[avcodec]:%arch%-windows + - vcpkg install libde265:%arch%-windows + - vcpkg install libjpeg-turbo:%arch%-windows + - vcpkg install libpng:%arch%-windows + - vcpkg install tiff:%arch%-windows + - ps: If (${env:arch} -eq "x64") { vcpkg install x265:${env:arch}-windows } + - vcpkg install zlib:%arch%-windows - cd c:\tools\vcpkg - vcpkg integrate install - cd %APPVEYOR_BUILD_FOLDER% @@ -24,9 +26,10 @@ 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 build: + parallel: true verbosity: normal artifacts: diff --git a/build-emscripten.sh b/build-emscripten.sh index f9449c2888..9e9ccaee2e 100755 --- a/build-emscripten.sh +++ b/build-emscripten.sh @@ -20,12 +20,14 @@ SRCDIR=$1 CORES="${CORES:-`nproc --all`}" ENABLE_LIBDE265="${ENABLE_LIBDE265:-1}" -LIBDE265_VERSION="${LIBDE265_VERSION:-1.0.12}" +LIBDE265_VERSION="${LIBDE265_VERSION:-1.0.15}" ENABLE_AOM="${ENABLE_AOM:-0}" AOM_VERSION="${AOM_VERSION:-3.6.1}" STANDALONE="${STANDALONE:-0}" DEBUG="${DEBUG:-0}" USE_WASM="${USE_WASM:-1}" +USE_TYPESCRIPT="${USE_TYPESCRIPT:-1}" +USE_UNSAFE_EVAL="${USE_UNSAFE_EVAL:-1}" echo "Build using ${CORES} CPU cores" @@ -46,9 +48,10 @@ if [ "$ENABLE_LIBDE265" = "1" ]; then emmake make -j${CORES} cd .. fi - CONFIGURE_ARGS_LIBDE265="-DLIBDE265_INCLUDE_DIR=${DIR}/libde265-${LIBDE265_VERSION} -DLIBDE265_LIBRARY=-L${DIR}/libde265-${LIBDE265_VERSION}/libde265/.libs" + 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 -lde265" - LIBRARY_INCLUDE_FLAGS="$LIBRARY_INCLUDE_FLAGS -L${DIR}/libde265-${LIBDE265_VERSION}/libde265/.libs" + LIBRARY_INCLUDE_FLAGS="$LIBRARY_INCLUDE_FLAGS -L${LIBDE265_DIR}/libde265/.libs" fi CONFIGURE_ARGS_AOM="" @@ -80,9 +83,10 @@ if [ "$ENABLE_AOM" = "1" ]; then cd .. fi - CONFIGURE_ARGS_AOM="-DAOM_INCLUDE_DIR=${DIR}/aom-${AOM_VERSION}/aom-source -DAOM_LIBRARY=-L${DIR}/aom-${AOM_VERSION}" + 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 -laom" - LIBRARY_INCLUDE_FLAGS="$LIBRARY_INCLUDE_FLAGS -L${DIR}/aom-${AOM_VERSION}" + LIBRARY_INCLUDE_FLAGS="$LIBRARY_INCLUDE_FLAGS -L${AOM_DIR}" fi EXTRA_EXE_LINKER_FLAGS="-lembind" @@ -108,7 +112,12 @@ EXPORTED_FUNCTIONS=$($EMSDK/upstream/bin/llvm-nm $LIBHEIFA --format=just-symbols echo "Running Emscripten..." -BUILD_FLAGS="-lembind -o libheif.js --post-js ${SRCDIR}/post.js -sWASM=$USE_WASM" +BUILD_FLAGS="-lembind -o libheif.js --post-js ${SRCDIR}/post.js -sWASM=$USE_WASM -sDYNAMIC_EXECUTION=$USE_UNSAFE_EVAL" + +if [ "$USE_TYPESCRIPT" = "1" ]; then + BUILD_FLAGS="$BUILD_FLAGS --emit-tsd libheif.d.ts" +fi + RELEASE_BUILD_FLAGS="-O3" if [ "$STANDALONE" = "1" ]; then @@ -128,7 +137,6 @@ emcc -Wl,--whole-archive "$LIBHEIFA" -Wl,--no-whole-archive \ -sEXPORT_NAME="libheif" \ -sWASM_ASYNC_COMPILATION=0 \ -sALLOW_MEMORY_GROWTH \ - --memory-init-file 0 \ -std=c++11 \ $LIBRARY_INCLUDE_FLAGS \ $LIBRARY_LINKER_FLAGS \ 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/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/examples/CMakeLists.txt b/examples/CMakeLists.txt index bdda0eb9bc..e76c15c232 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -20,21 +20,17 @@ install(FILES heif-info.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) add_executable(heif-dec ${getopt_sources} - encoder.cc - encoder.h - encoder_y4m.cc - encoder_y4m.h heif_dec.cc - ../libheif/exif.cc common.cc common.h) -target_link_libraries(heif-dec PRIVATE heif) +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 ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/heif-dec${CMAKE_EXECUTABLE_SUFFIX} ${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_BINDIR}/heif-convert${CMAKE_EXECUTABLE_SUFFIX})") + 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() @@ -42,83 +38,34 @@ endif() add_executable(heif-enc ${getopt_sources} heif_enc.cc - ../libheif/exif.cc - ../libheif/exif.cc benchmark.h benchmark.cc - encoder.cc - decoder_y4m.cc - decoder_y4m.h common.cc common.h) -target_link_libraries(heif-enc PRIVATE heif) +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) - -add_executable(heif-test ${getopt_sources} - heif_test.cc - common.cc - common.h) -target_link_libraries(heif-test heif) - - -find_package(JPEG) -if (TARGET JPEG::JPEG) - add_definitions(-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(heif-dec PRIVATE JPEG::JPEG) - target_link_libraries(heif-enc PRIVATE JPEG::JPEG) - - target_sources(heif-dec PRIVATE encoder_jpeg.cc encoder_jpeg.h) - target_sources(heif-enc PRIVATE decoder.h decoder_jpeg.cc decoder_jpeg.h) +if (WITH_HEADER_COMPRESSION) + target_compile_definitions(heif-enc PRIVATE WITH_HEADER_COMPRESSION=1) endif () -find_package(PNG) -if (TARGET PNG::PNG) - add_definitions(-DHAVE_LIBPNG=1) - - target_link_libraries(heif-dec PRIVATE PNG::PNG) - target_link_libraries(heif-enc PRIVATE PNG::PNG) - - target_sources(heif-dec PRIVATE encoder_png.cc encoder_png.h) - target_sources(heif-enc PRIVATE decoder_png.cc decoder_png.h) - +if (PNG_FOUND) add_executable(heif-thumbnailer ${getopt_sources} - encoder.cc - encoder.h heif_thumbnailer.cc - encoder_png.cc - encoder_png.h - ../libheif/exif.h - ../libheif/exif.cc common.cc common.h) - target_link_libraries(heif-thumbnailer heif PNG::PNG) + 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 () +endif() + +add_executable(heif-test ${getopt_sources} + heif_test.cc + common.cc + common.h) +target_link_libraries(heif-test heif) 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/geoheif_create.cc b/examples/geoheif_create.cc new file mode 100644 index 0000000000..25b9a1d3d4 --- /dev/null +++ b/examples/geoheif_create.cc @@ -0,0 +1,1087 @@ +/* + libheif example application "heif". + + MIT License + + 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 + 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 "libheif/heif_items.h" + +#include "heifio/decoder_jpeg.h" +#include "heifio/decoder_png.h" +#include "heifio/decoder_tiff.h" +#include "heifio/decoder_y4m.h" + +#include "benchmark.h" +#include "common.h" + +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; +int metadata_compression = 0; +const char* encoderId = nullptr; +std::string chroma_downsampling; + +uint16_t nclx_colour_primaries = 1; +uint16_t nclx_transfer_characteristic = 13; +uint16_t nclx_matrix_coefficients = 6; +int nclx_full_range = true; + +std::string property_pitm_description; + +// for benchmarking + +#if !defined(_MSC_VER) +#define HAVE_GETTIMEOFDAY 1 // TODO: should be set by CMake +#endif + +#if HAVE_GETTIMEOFDAY + +#include + +struct timeval time_encoding_start; +struct timeval time_encoding_end; +#endif + +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; + + +static struct 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) "vvc", no_argument, 0, OPTION_USE_VVC_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'}, +#endif + {(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}, + {(char* const) "plugin-directory", required_argument, 0, OPTION_PLUGIN_DIRECTORY}, + {(char* const) "benchmark", no_argument, &run_benchmark, 1}, + {(char* const) "enable-metadata-compression", no_argument, &metadata_compression, 1}, + {(char* const) "pitm-description", required_argument, 0, OPTION_PITM_DESCRIPTION}, + {(char* const) "chroma-downsampling", required_argument, 0, 'C'}, + {0, 0, 0, 0}, +}; + + +void show_help(const char* argv0) +{ + 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" + << " -v, --version show version\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" + << " -o, --output output filename (optional)\n" + << " --verbose enable logging output (more will increase logging level)\n" + << " -P, --params show all encoder parameters and exit, input file not required or used.\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 (not needed if output filename with .avif suffix is provided)\n" + << " --vvc encode as VVC (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" +#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" + << " -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 are present\n" + << " --premultiplied-alpha input image has premultiplied alpha\n" +#if WITH_HEADER_COMPRESSION + << " --enable-metadata-compression enable XMP metadata compression (experimental)\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 (experimental) set user description for primary image\n"; +} + + +void list_encoder_parameters(heif_encoder* encoder) +{ + std::cerr << "Parameters for encoder `" << heif_encoder_get_name(encoder) << "`:\n"; + + 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]); + + switch (heif_encoder_parameter_get_type(params[i])) { + case heif_encoder_parameter_type_integer: { + heif_error error; + + std::cerr << " " << name; + + if (heif_encoder_has_default(encoder, name)) { + int value; + error = heif_encoder_get_parameter_integer(encoder, name, &value); + (void) error; + + std::cerr << ", default=" << value; + } + + 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); + + if (have_minimum || have_maximum) { // TODO: only one is set + std::cerr << ", [" << minimum << ";" << maximum << "]"; + } + + if (num_valid_values > 0) { + std::cerr << ", {"; + + for (int p = 0; p < num_valid_values; p++) { + if (p > 0) { + std::cerr << ", "; + } + + std::cerr << valid_values[p]; + } + + std::cerr << "}"; + } + + std::cerr << "\n"; + } + break; + + case heif_encoder_parameter_type_boolean: { + heif_error error; + std::cerr << " " << name; + + if (heif_encoder_has_default(encoder, name)) { + int value; + error = heif_encoder_get_parameter_boolean(encoder, name, &value); + (void) error; + + std::cerr << ", default=" << (value ? "true" : "false"); + } + + std::cerr << "\n"; + } + break; + + 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; + + std::cerr << ", default=" << value; + } + + const char* const* valid_options; + error = heif_encoder_parameter_string_valid_values(encoder, name, &valid_options); + + 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; + } + } +} + + +void set_params(struct 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); + } + + std::string name = p.substr(0, pos); + std::string value = p.substr(pos + 1); + + 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); + } + } +} + + +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 (i == 0) { + std::cout << " [default]"; + } + + std::cout << "\n"; + } +} + + +static const char* get_compression_format_name(heif_compression_format format) +{ + 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: +#if WITH_UNCOMPRESSED_CODEC + std::cout << "Uncompressed: yes\n"; +#else + std::cout << "Uncompressed: no\n"; +#endif + continue; // special handling of this case because it is built in without plugin + break; + case heif_compression_VVC: + std::cout << "VVIC"; + break; + default: + assert(false); + } + + std::cout << " encoders:\n"; + +#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 + + show_list_of_encoders(encoder_descriptors, count); + } +} + + +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; + } +} + + +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, ".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_HEVC: return "heic"; + case heif_compression_JPEG2000: return "hej2"; + default: return "data"; + } +} + + +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 = 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_vvc = false; + bool force_enc_uncompressed = false; + bool force_enc_jpeg = false; + bool force_enc_jpeg2000 = false; + bool force_enc_htj2k = false; + bool crop_to_even_size = false; + + std::vector raw_params; + + + while (true) { + int option_index = 0; + int c = getopt_long(argc, argv, "hq:Lo:vPp:t:b:AEe:C:" +#if WITH_UNCOMPRESSED_CODEC + "U" +#endif + , long_options, &option_index); + if (c == -1) + break; + + switch (c) { + case 'h': + show_help(argv[0]); + return 0; + case 'v': + show_version(); + return 0; + case 'q': + quality = atoi(optarg); + break; + case 'L': + lossless = true; + break; + case 'o': + output_filename = optarg; + break; + case OPTION_VERBOSE: + logging_level++; + break; + case 'P': + option_show_parameters = true; + break; + case 'p': + raw_params.push_back(optarg); + break; + case 't': + thumbnail_bbox_size = atoi(optarg); + break; + case 'b': + output_bit_depth = atoi(optarg); + break; + case 'A': + force_enc_av1f = true; + break; +#if WITH_UNCOMPRESSED_CODEC + case 'U': + force_enc_uncompressed = true; + break; +#endif + case 'E': + crop_to_even_size = true; + break; + case 'e': + encoderId = optarg; + break; + case OPTION_NCLX_MATRIX_COEFFICIENTS: + nclx_matrix_coefficients = (uint16_t) strtoul(optarg, nullptr, 0); + break; + case OPTION_NCLX_COLOUR_PRIMARIES: + nclx_colour_primaries = (uint16_t) strtoul(optarg, nullptr, 0); + break; + case OPTION_NCLX_TRANSFER_CHARACTERISTIC: + 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_VVC_COMPRESSION: + force_enc_vvc = 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.\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 '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; + } + } + + 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) > 1) { + std::cerr << "Choose at most one output compression format.\n"; + } + + if (logging_level > 0) { + logging_level += 2; + + if (logging_level > 4) { + logging_level = 4; + } + } + + + // ============================================================================== + + 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_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 { + 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) { + std::cerr << "Could not create context object\n"; + return 1; + } + + +#define MAX_ENCODERS 10 + const heif_encoder_descriptor* encoder_descriptors[MAX_ENCODERS]; + 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) { + std::cerr << "Unknown encoder ID. Choose one from the list below.\n"; + show_list_of_encoders(encoder_descriptors, count); + return 5; + } + + if (strcmp(encoderId, heif_encoder_descriptor_get_id_name(encoder_descriptors[i])) == 0) { + idx = i; + break; + } + } + } + + heif_error error = heif_context_get_encoder(context.get(), encoder_descriptors[idx], &encoder); + if (error.code) { + std::cerr << error.message << "\n"; + return 5; + } + + active_encoder_descriptor = encoder_descriptors[idx]; + } + else { + 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; + } + + + // 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--; + } + } + + struct heif_error error; + + std::shared_ptr primary_image; + + for (; optind < argc; optind++) { + std::string input_filename = 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; + } + + std::string suffix = suffix_for_compression_format(compressionFormat); + output_filename = filename_without_suffix + '.' + suffix; + } + + + // ============================================================================== + + // 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); + } + + enum + { + PNG, JPEG, Y4M, TIFF + } filetype = JPEG; + if (suffix == "png") { + filetype = PNG; + } + else if (suffix == "y4m") { + filetype = Y4M; + } else if (suffix == "tif" || suffix == "tiff") { + filetype = TIFF; + } + + InputImage input_image; + 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 TIFF input_image: " << err.message << std::endl; + 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 TIFF input_image: " << err.message << std::endl; + exit(1); + } + } + else if (filetype == TIFF) { + heif_error err = loadTIFF(input_filename.c_str(), &input_image); + if (err.code != heif_error_Ok) { + std::cerr << "Can not load TIFF input_image: " << err.message << std::endl; + 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 << std::endl; + exit(1); + } + } + + std::shared_ptr image = input_image.image; + + if (!primary_image) { + primary_image = image; + } + +#if HAVE_GETTIMEOFDAY + if (run_benchmark) { + gettimeofday(&time_encoding_start, nullptr); + } +#endif + + heif_color_profile_nclx* nclx = heif_nclx_color_profile_alloc(); + if (!nclx) { + std::cerr << "Cannot allocate NCLX color profile.\n"; + exit(5); + } + + if (lossless) { + if (heif_encoder_descriptor_supports_lossless_compression(active_encoder_descriptor)) { + heif_encoder_set_lossless(encoder, true); + + if (heif_image_get_colorspace(primary_image.get()) == heif_colorspace_RGB) { + nclx->matrix_coefficients = heif_matrix_coefficients_RGB_GBR; + nclx->full_range_flag = true; + raw_params.emplace_back("chroma=444"); + } + else { + heif_color_profile_nclx* input_nclx; + + error = heif_image_get_nclx_color_profile(primary_image.get(), &input_nclx); + if (error.code == heif_error_Color_profile_does_not_exist) { + // NOP, use default NCLX profile + } + else if (error.code) { + std::cerr << "Cannot get input NCLX color profile.\n"; + exit(5); + } + else { + 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; + + heif_nclx_color_profile_free(input_nclx); + } + + // 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(primary_image.get())) { + case heif_chroma_420: + case heif_chroma_monochrome: + raw_params.emplace_back("chroma=420"); + break; + case heif_chroma_422: + raw_params.emplace_back("chroma=422"); + break; + case heif_chroma_444: + raw_params.emplace_back("chroma=444"); + break; + default: + assert(false); + exit(5); + } + } + } + else { + std::cerr << "Warning: the selected encoder does not support lossless encoding. Encoding in lossy mode.\n"; + lossless = false; + } + } + + if (!lossless) { + error = heif_nclx_color_profile_set_matrix_coefficients(nclx, nclx_matrix_coefficients); + if (error.code) { + std::cerr << "Invalid matrix coefficients specified.\n"; + exit(5); + } + error = heif_nclx_color_profile_set_transfer_characteristics(nclx, nclx_transfer_characteristic); + if (error.code) { + std::cerr << "Invalid transfer characteristics specified.\n"; + exit(5); + } + error = heif_nclx_color_profile_set_color_primaries(nclx, nclx_colour_primaries); + if (error.code) { + std::cerr << "Invalid color primaries specified.\n"; + exit(5); + } + nclx->full_range_flag = (uint8_t) nclx_full_range; + + 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_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; + options->image_orientation = input_image.orientation; + + 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; + } + + 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"; + heif_encoder_release(encoder); + return 1; + } + + 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; + + error = heif_image_crop(image.get(), 0, right, 0, bottom); + if (error.code != 0) { + heif_encoding_options_free(options); + heif_nclx_color_profile_free(nclx); + heif_encoder_release(encoder); + std::cerr << "Could not crop image: " << error.message << "\n"; + return 1; + } + } + + if (premultiplied_alpha) { + heif_image_set_premultiplied_alpha(image.get(), premultiplied_alpha); + } + + + 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); + heif_nclx_color_profile_free(nclx); + heif_encoder_release(encoder); + std::cerr << "Could not encode HEIF/AVIF file: " << error.message << "\n"; + return 1; + } + + // 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.get(), handle, + input_image.exif.data(), (int) input_image.exif.size()); + if (error.code != 0) { + heif_encoding_options_free(options); + heif_nclx_color_profile_free(nclx); + heif_encoder_release(encoder); + 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.get(), handle, + input_image.xmp.data(), (int) input_image.xmp.size(), + metadata_compression ? heif_metadata_compression_deflate : heif_metadata_compression_off); + if (error.code != 0) { + heif_encoding_options_free(options); + heif_nclx_color_profile_free(nclx); + heif_encoder_release(encoder); + std::cerr << "Could not write XMP metadata: " << error.message << "\n"; + return 1; + } + } + + if (thumbnail_bbox_size > 0) { + // encode thumbnail + + struct heif_image_handle* thumbnail_handle; + + options->save_alpha_channel = master_alpha && thumb_alpha; + + error = heif_context_encode_thumbnail(context.get(), + image.get(), + handle, + encoder, + options, + thumbnail_bbox_size, + &thumbnail_handle); + if (error.code) { + heif_encoding_options_free(options); + heif_nclx_color_profile_free(nclx); + heif_encoder_release(encoder); + std::cerr << "Could not generate thumbnail: " << error.message << "\n"; + return 5; + } + + if (thumbnail_handle) { + heif_image_handle_release(thumbnail_handle); + } + } + +#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); + } + + 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.get(), &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.get(), pitm_id, &udes, nullptr); + if (err.code) { + std::cerr << "Cannot set user description\n"; + return 5; + } + + heif_image_handle_release(primary_image_handle); + } + + error = heif_context_write_to_file(context.get(), output_filename.c_str()); + if (error.code) { + std::cerr << error.message << "\n"; + return 5; + } + + 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; +} diff --git a/examples/heif_dec.cc b/examples/heif_dec.cc index eaff6e79fc..0353dcc905 100644 --- a/examples/heif_dec.cc +++ b/examples/heif_dec.cc @@ -1,8 +1,9 @@ /* - libheif example application "convert". + 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 @@ -36,6 +37,7 @@ #include #include +#include #include #include #include @@ -45,20 +47,21 @@ #include -#include "encoder.h" +#include "heifio/encoder.h" #if HAVE_LIBJPEG - -#include "encoder_jpeg.h" - +#include "heifio/encoder_jpeg.h" #endif -#if HAVE_LIBPNG -#include "encoder_png.h" +#if HAVE_LIBPNG +#include "heifio/encoder_png.h" +#endif +#if HAVE_LIBTIFF +#include "heifio/encoder_tiff.h" #endif -#include "encoder_y4m.h" +#include "../heifio/encoder_y4m.h" #include "common.h" #if defined(_MSC_VER) @@ -74,7 +77,7 @@ static void show_help(const char* argv0) "Usage: " << argv0 << " [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, y4m. If no output filename is specified, 'jpg' is used.\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" @@ -88,6 +91,7 @@ static void show_help(const char* argv0) " --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" " -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"; @@ -118,6 +122,7 @@ 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; std::string output_filename; std::string chroma_upsampling; @@ -137,6 +142,7 @@ static struct option long_options[] = { {(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) "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}, @@ -164,22 +170,22 @@ void list_decoders(heif_compression_format format) void list_all_decoders() { - std::cout << "HEIC decoders:\n"; - list_decoders(heif_compression_HEVC); - - std::cout << "VVIC decoders:\n"; - list_decoders(heif_compression_VVC); + 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 << "HT-J2K decoders:\n"; + std::cout << "JPEG 2000 (HT) decoders:\n"; list_decoders(heif_compression_HTJ2K); #if WITH_UNCOMPRESSED_CODEC @@ -187,6 +193,9 @@ void list_all_decoders() #else std::cout << "uncompressed: no\n"; #endif + + std::cout << "VVIC decoders:\n"; + list_decoders(heif_compression_VVC); } @@ -217,6 +226,365 @@ void show_png_compression_level_usage_warning() } +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); + + struct heif_image* image; + struct 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; + + struct heif_image_handle* depth_handle; + 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); + + 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); + 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) { + + struct heif_image_handle* aux_handle; + 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); + + 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); + 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 << "-" + 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 = (exif[0] << 24) | (exif[1] << 16) | (exif[2] << 8) | 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++) { + struct heif_image* image; + struct 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); } @@ -254,7 +622,7 @@ int main(int argc, char** argv) break; case '?': std::cerr << "\n"; - // fallthrough + [[fallthrough]]; case 'h': show_help(argv[0]); return 0; @@ -362,6 +730,15 @@ int main(int argc, char** argv) #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()); } @@ -384,6 +761,10 @@ int main(int argc, char** argv) // TODO: check, whether reading from named pipes works at all. 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 = (length[0] << 24) + (length[1] << 16) + (length[2] << 8) + (length[3]); @@ -452,10 +833,10 @@ int main(int argc, char** argv) numbered_output_filename_stem = s.str(); s << "." << output_filename_suffix; - filename.assign(s.str()); + filename = s.str(); } else { - filename.assign(output_filename); + filename = output_filename; numbered_output_filename_stem = output_filename_stem; } @@ -467,12 +848,14 @@ int main(int argc, char** argv) 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); + 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->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; @@ -483,258 +866,21 @@ int main(int argc, char** argv) decode_options->color_conversion_options.only_use_preferred_chroma_algorithm = true; } - 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; - } + int ret; - 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 (option_output_tiles) { + ret = decode_image_tiles(handle, numbered_output_filename_stem, output_filename_suffix, decode_options.get(), encoder); } - - // 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"; + else { + ret = decode_single_image(handle, numbered_output_filename_stem, output_filename_suffix, decode_options.get(), encoder); } - - if (image) { - 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; - - 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 << numbered_output_filename_stem; - s << "-depth."; - s << output_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) { - - 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_release(aux_image); - 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_release_auxiliary_type(aux_handle, &auxTypeC); - - std::ostringstream s; - s << numbered_output_filename_stem; - s << "-" + auxType + "."; - s << output_filename_suffix; - - std::string auxFilename = s.str(); - - if (option_no_colons) { - std::replace(auxFilename.begin(), auxFilename.end(), ':', '_'); - } - - 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) { - heif_image_handle_release(handle); - std::cerr << "Could not read XMP metadata: " << err.message << "\n"; - return 1; - } - - // write XMP data to file - - std::string xmp_filename = numbered_output_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) { - heif_image_handle_release(handle); - std::cerr << "Could not read EXIF metadata: " << err.message << "\n"; - return 1; - } - - uint32_t offset = 0; - if (option_skip_exif_offset) { - if (exifSize<4) { - heif_image_handle_release(handle); - std::cerr << "Invalid EXIF metadata, it is too small.\n"; - return 1; - } - - offset = (exif[0]<<24) | (exif[1]<<16) | (exif[2]<<8) | exif[3]; - offset += 4; - - if (offset >= exifSize) { - heif_image_handle_release(handle); - std::cerr << "Invalid EXIF metadata, offset out of range.\n"; - return 1; - } - } - - // write EXIF data to file - - std::string exif_filename = numbered_output_filename_stem + ".exif"; - std::ofstream ostr(exif_filename.c_str()); - ostr.write((char*)exif.data() + offset, exifSize - offset); - } - } - } - } - + if (ret) { heif_image_handle_release(handle); + return ret; } + heif_image_handle_release(handle); + image_index++; } diff --git a/examples/heif_enc.cc b/examples/heif_enc.cc index 5115840c5b..5a5273cd93 100644 --- a/examples/heif_enc.cc +++ b/examples/heif_enc.cc @@ -24,8 +24,9 @@ SOFTWARE. */ -#include -#include +#include +#include +#include #include #include @@ -35,25 +36,20 @@ #include #include #include +#include #include #include #include "libheif/heif_items.h" -#if HAVE_LIBJPEG -#include "decoder_jpeg.h" -#endif - -#if HAVE_LIBPNG -#include "decoder_png.h" -#endif - -#include "decoder_y4m.h" +#include "heifio/decoder_jpeg.h" +#include "heifio/decoder_png.h" +#include "heifio/decoder_tiff.h" +#include "heifio/decoder_y4m.h" -#include #include "benchmark.h" -#include "exif.h" #include "common.h" +#include "libheif/heif_experimental.h" int master_alpha = 1; int thumb_alpha = 1; @@ -62,8 +58,14 @@ int two_colr_boxes = 0; int premultiplied_alpha = 0; int run_benchmark = 0; int metadata_compression = 0; +int tiled_input_x_y = 0; const char* encoderId = nullptr; std::string chroma_downsampling; +int tiled_image_width = 0; +int tiled_image_height = 0; +std::string tiling_method = "grid"; +heif_metadata_compression unci_compression = heif_metadata_compression_brotli; +int add_pyramid_group = 0; uint16_t nclx_colour_primaries = 1; uint16_t nclx_transfer_characteristic = 13; @@ -81,6 +83,9 @@ std::string property_pitm_description; #if HAVE_GETTIMEOFDAY #include +#include +#include +#include struct timeval time_encoding_start; struct timeval time_encoding_end; @@ -97,6 +102,10 @@ 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; static struct option long_options[] = { @@ -121,6 +130,7 @@ static struct option long_options[] = { {(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}, #endif {(char* const) "matrix_coefficients", required_argument, 0, OPTION_NCLX_MATRIX_COEFFICIENTS}, {(char* const) "colour_primaries", required_argument, 0, OPTION_NCLX_COLOUR_PRIMARIES}, @@ -133,6 +143,12 @@ static struct option long_options[] = { {(char* const) "enable-metadata-compression", no_argument, &metadata_compression, 1}, {(char* const) "pitm-description", required_argument, 0, OPTION_PITM_DESCRIPTION}, {(char* const) "chroma-downsampling", required_argument, 0, 'C'}, + {(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}, {0, 0, 0, 0}, }; @@ -169,46 +185,39 @@ void show_help(const char* argv0) << " --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" + << " -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" - << " -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 are present\n" << " --premultiplied-alpha input image has premultiplied alpha\n" +#if WITH_HEADER_COMPRESSION << " --enable-metadata-compression enable XMP metadata compression (experimental)\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 (experimental) set user description for primary image\n"; -} - - -#if !HAVE_LIBJPEG -InputImage loadJPEG(const char* filename) -{ - std::cerr << "Cannot load JPEG because libjpeg support was not compiled.\n"; - exit(1); - - return {}; -} + << " --pitm-description TEXT (experimental) set user description for primary image\n" + << " --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 WITH_EXPERIMENTAL_FEATURES + << " --tiling-method METHOD choose one of these methods: grid, tili, unci. The default is 'grid'.\n" + << " --add-pyramid-group when several images are given, put them into a multi-resolution pyramid group.\n" #endif - - -#if !HAVE_LIBPNG -InputImage loadPNG(const char* filename, int output_bit_depth) -{ - std::cerr << "Cannot load PNG because libpng support was not compiled.\n"; - exit(1); - - return {}; + ; } -#endif void list_encoder_parameters(heif_encoder* encoder) @@ -356,6 +365,9 @@ static const char* get_compression_format_name(heif_compression_format format) case heif_compression_AV1: return "AV1"; break; + case heif_compression_AVC: + return "AVC"; + break; case heif_compression_VVC: return "VVC"; break; @@ -382,19 +394,18 @@ static const char* get_compression_format_name(heif_compression_format format) static void show_list_of_all_encoders() { - for (auto compression_format : {heif_compression_HEVC, heif_compression_AV1, heif_compression_VVC, heif_compression_JPEG, heif_compression_JPEG2000, heif_compression_HTJ2K -#if WITH_UNCOMPRESSED_CODEC -, heif_compression_uncompressed -#endif + 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_VVC: - std::cout << "VVIC"; - break; case heif_compression_HEVC: std::cout << "HEIC"; break; @@ -405,10 +416,18 @@ static void show_list_of_all_encoders() std::cout << "JPEG 2000"; break; case heif_compression_HTJ2K: - std::cout << "HT-J2K"; + std::cout << "JPEG 2000 (HT)"; break; case heif_compression_uncompressed: - std::cout << "Uncompressed"; +#if WITH_UNCOMPRESSED_CODEC + std::cout << "Uncompressed: yes\n"; +#else + std::cout << "Uncompressed: no\n"; +#endif + continue; // special handling of this case because it is built in without plugin + break; + case heif_compression_VVC: + std::cout << "VVIC"; break; default: assert(false); @@ -474,6 +493,371 @@ std::string suffix_for_compression_format(heif_compression_format format) } +InputImage load_image(const std::string& input_filename, int output_bit_depth) +{ + InputImage input_image; + + // 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); + } + + enum + { + PNG, JPEG, Y4M, TIFF + } filetype = JPEG; + if (suffix == "png") { + filetype = PNG; + } + else if (suffix == "y4m") { + filetype = Y4M; + } + else if (suffix == "tif" || suffix == "tiff") { + filetype = TIFF; + } + + 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 TIFF 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 TIFF input_image: " << err.message << '\n'; + exit(1); + } + } + else if (filetype == TIFF) { + heif_error err = loadTIFF(input_filename.c_str(), &input_image); + if (err.code != heif_error_Ok) { + std::cerr << "Can not load TIFF 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; +} + + +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) +{ + *out_nclx = heif_nclx_color_profile_alloc(); + if (!*out_nclx) { + return {heif_error_Encoding_error, heif_suberror_Unspecified, "Cannot allocate NCLX color profile."}; + } + + heif_color_profile_nclx* nclx = *out_nclx; // abbreviation; + + if (lossless) { + heif_encoder_set_lossless(encoder, true); + + if (heif_image_get_colorspace(input_image.get()) == heif_colorspace_RGB) { + nclx->matrix_coefficients = heif_matrix_coefficients_RGB_GBR; + nclx->full_range_flag = true; + + heif_error error = heif_encoder_set_parameter(encoder, "chroma", "444"); + if (error.code) { + return error; + } + } + else { + heif_color_profile_nclx* input_nclx; + + heif_error error = heif_image_get_nclx_color_profile(input_image.get(), &input_nclx); + if (error.code == heif_error_Color_profile_does_not_exist) { + // NOP, use default NCLX profile + } + else if (error.code) { + std::cerr << "Cannot get input NCLX color profile.\n"; + return error; + } + else { + 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; + + heif_nclx_color_profile_free(input_nclx); + } + + // 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); + } + + if (error.code) { + return error; + } + } + } + + if (!lossless) { + 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); + } + error = heif_nclx_color_profile_set_transfer_characteristics(nclx, nclx_transfer_characteristic); + if (error.code) { + std::cerr << "Invalid transfer characteristics specified.\n"; + exit(5); + } + error = heif_nclx_color_profile_set_color_primaries(nclx, nclx_colour_primaries); + if (error.code) { + std::cerr << "Invalid color primaries specified.\n"; + exit(5); + } + nclx->full_range_flag = (uint8_t) nclx_full_range; + } + + return {heif_error_Ok}; +} + + +struct input_tiles_generator +{ + 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::string directory; + std::string prefix; + std::string separator; + std::string suffix; + + bool first_is_x = false; + + uint32_t nColumns() const { return first_is_x ? (first_end - first_start + 1) : (second_end - second_start + 1); } + uint32_t nRows() const { return first_is_x ? (second_end - second_start + 1) : (first_end - first_start + 1); } + + uint32_t nTiles() const { return (first_end - first_start + 1) * (second_end - second_start + 1); } + + std::string filename(uint32_t tx, uint32_t ty) const + { + std::stringstream sstr; + if (!directory.empty()) { + sstr << directory << '/'; + } + 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; + return sstr.str(); + } +}; + +std::optional determine_input_images_tiling(const std::string& filename) +{ + std::regex pattern(R"((.*\D)?(\d+)(\D+?)(\d+)(\..+)$)"); + std::smatch match; + + input_tiles_generator generator; + + if (std::regex_match(filename, match, pattern)) { + generator.prefix = match[1]; + generator.separator = match[3]; + generator.suffix = match[5]; + + generator.first_start = 9999; + generator.first_end = 0; + generator.first_digits = 9; + + generator.second_start = 9999; + generator.second_end = 0; + generator.second_digits = 9; + } + else { + return std::nullopt; + } + + auto p = generator.prefix.find_last_of('/'); + if (p != std::string::npos) { + generator.directory = generator.prefix.substr(0,p); + generator.prefix = generator.prefix.substr(p+1); + } + + std::string patternString = generator.prefix + "(\\d+)" + generator.separator + "(\\d+)" + generator.suffix + "$"; + pattern = patternString; + + std::regex dirPattern(R"((.*)/(.*?)$)"); + std::smatch dirmatch; + std::string dirName; + + if (std::regex_match(filename, dirmatch, dirPattern)) { + dirName = std::string(dirmatch[1]); + } + else { + dirName = "."; + } + + DIR* dir = opendir(dirName.c_str()); + for (;;) { + struct dirent* entry = readdir(dir); + if (!entry) { + break; + } + + if (entry->d_type == DT_REG) { + std::string s{entry->d_name}; + + if (std::regex_match(s, match, pattern)) { + uint32_t first = std::stoi(match[1]); + uint32_t second = std::stoi(match[2]); + + 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()); + + 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); + } + } + } + + closedir(dir); + + return generator; +} + + +heif_image_handle* encode_tiled(heif_context* ctx, heif_encoder* encoder, heif_encoding_options* options, + int output_bit_depth, + const input_tiles_generator& tile_generator, + const heif_image_tiling& tiling) +{ + heif_image_handle* tiled_image; + + + // --- create the main grid image + + 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 WITH_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; + } + } + 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; + + std::string input_filename = tile_generator.filename(0, 0); + InputImage prototype_image = load_image(input_filename, output_bit_depth); + + heif_error error = heif_context_add_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); + } + + + // --- 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"; + + uint32_t tile_width = 0, tile_height = 0; + + for (uint32_t ty = 0; ty < tile_generator.nRows(); ty++) + for (uint32_t tx = 0; tx < tile_generator.nColumns(); tx++) { + std::string input_filename = tile_generator.filename(tx,ty); + + InputImage input_image = load_image(input_filename, output_bit_depth); + + if (tile_width == 0) { + tile_width = heif_image_get_primary_width(input_image.image.get()); + tile_height = heif_image_get_primary_height(input_image.image.get()); + } + + 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(); + + 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; + } + } + + std::cout << "\n"; + + return tiled_image; +} + + class LibHeifInitializer { public: @@ -501,14 +885,14 @@ int main(int argc, char** argv) bool force_enc_jpeg = false; bool force_enc_jpeg2000 = false; bool force_enc_htj2k = false; - bool crop_to_even_size = false; + bool use_tiling = false; std::vector raw_params; while (true) { int option_index = 0; - int c = getopt_long(argc, argv, "hq:Lo:vPp:t:b:AEe:C:" + int c = getopt_long(argc, argv, "hq:Lo:vPp:t:b:Ae:C:T" #if WITH_UNCOMPRESSED_CODEC "U" #endif @@ -555,9 +939,6 @@ int main(int argc, char** argv) force_enc_uncompressed = true; break; #endif - case 'E': - crop_to_even_size = true; - break; case 'e': encoderId = optarg; break; @@ -602,6 +983,43 @@ int main(int argc, char** argv) } 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_EXPERIMENTAL_FEATURES + && tiling_method != "tili" && tiling_method != "unci" +#endif + ) { + std::cerr << "Invalid tiling method '" << tiling_method << "'\n"; + exit(5); + } + break; + case OPTION_UNCI_COMPRESSION: { + std::string option(optarg); + if (option == "none") { + unci_compression = heif_metadata_compression_off; + } + else if (option == "brotli") { + unci_compression = heif_metadata_compression_brotli; + } + else if (option == "deflate") { + unci_compression = heif_metadata_compression_deflate; + } + else if (option == "zlib") { + unci_compression = heif_metadata_compression_zlib; + } + else { + std::cerr << "Invalid unci compression method '" << option << "'\n"; + exit(5); + } + break; + } case 'C': chroma_downsampling = optarg; if (chroma_downsampling != "nn" && @@ -621,6 +1039,9 @@ int main(int argc, char** argv) } #endif break; + case 'T': + use_tiling = true; + break; } } @@ -629,7 +1050,7 @@ int main(int argc, char** argv) return 5; } - if ((force_enc_av1f ? 1 : 0) + (force_enc_vvc ? 1 : 0) + (force_enc_uncompressed ? 1 : 0) + (force_enc_jpeg ? 1 : 0) + + 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) > 1) { std::cerr << "Choose at most one output compression format.\n"; } @@ -743,6 +1164,11 @@ int main(int argc, char** argv) } + 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) { @@ -756,6 +1182,10 @@ int main(int argc, char** argv) std::shared_ptr primary_image; + bool is_primary_image = true; + + std::vector encoded_image_ids; + for (; optind < argc; optind++) { std::string input_filename = argv[optind]; @@ -776,38 +1206,36 @@ int main(int argc, char** argv) // ============================================================================== - // get file type from file name + InputImage input_image = load_image(input_filename, output_bit_depth); - 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); - } + std::shared_ptr image = input_image.image; - enum - { - PNG, JPEG, Y4M - } filetype = JPEG; - if (suffix == "png") { - filetype = PNG; - } - else if (suffix == "y4m") { - filetype = Y4M; - } + heif_image_tiling tiling{}; + std::optional tile_generator; + if (use_tiling) { + tile_generator = determine_input_images_tiling(input_filename); + if (tile_generator) { + tiling.version = 1; + + if (tiled_input_x_y) tile_generator->first_is_x = true; + + 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; + } - InputImage input_image; - if (filetype == PNG) { - input_image = loadPNG(input_filename.c_str(), output_bit_depth); - } - else if (filetype == Y4M) { - input_image = loadY4M(input_filename.c_str()); - } - else { - input_image = loadJPEG(input_filename.c_str()); - } + if (tiled_image_width) tiling.image_width = tiled_image_width; + if (tiled_image_height) tiling.image_height = tiled_image_height; - std::shared_ptr image = input_image.image; + 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; + } + } if (!primary_image) { primary_image = image; @@ -819,83 +1247,14 @@ int main(int argc, char** argv) } #endif - heif_color_profile_nclx* nclx = heif_nclx_color_profile_alloc(); - if (!nclx) { - std::cerr << "Cannot allocate NCLX color profile.\n"; - exit(5); - } - - if (lossless) { - if (heif_encoder_descriptor_supports_lossless_compression(active_encoder_descriptor)) { - heif_encoder_set_lossless(encoder, true); - - if (heif_image_get_colorspace(primary_image.get()) == heif_colorspace_RGB) { - nclx->matrix_coefficients = heif_matrix_coefficients_RGB_GBR; - nclx->full_range_flag = true; - raw_params.emplace_back("chroma=444"); - } - else { - heif_color_profile_nclx* input_nclx; - - error = heif_image_get_nclx_color_profile(primary_image.get(), &input_nclx); - if (error.code == heif_error_Color_profile_does_not_exist) { - // NOP, use default NCLX profile - } - else if (error.code) { - std::cerr << "Cannot get input NCLX color profile.\n"; - exit(5); - } - else { - 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; - - heif_nclx_color_profile_free(input_nclx); - } - - // 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(primary_image.get())) { - case heif_chroma_420: - case heif_chroma_monochrome: - raw_params.emplace_back("chroma=420"); - break; - case heif_chroma_422: - raw_params.emplace_back("chroma=422"); - break; - case heif_chroma_444: - raw_params.emplace_back("chroma=444"); - break; - default: - assert(false); - exit(5); - } - } - } - else { - std::cerr << "Warning: the selected encoder does not support lossless encoding. Encoding in lossy mode.\n"; - lossless = false; - } + heif_color_profile_nclx* nclx; + heif_error error = create_output_nclx_profile_and_configure_encoder(encoder, &nclx, primary_image, lossless); + if (error.code) { + std::cerr << error.message << "\n"; + return 5; } if (!lossless) { - error = heif_nclx_color_profile_set_matrix_coefficients(nclx, nclx_matrix_coefficients); - if (error.code) { - std::cerr << "Invalid matrix coefficients specified.\n"; - exit(5); - } - error = heif_nclx_color_profile_set_transfer_characteristics(nclx, nclx_transfer_characteristic); - if (error.code) { - std::cerr << "Invalid transfer characteristics specified.\n"; - exit(5); - } - error = heif_nclx_color_profile_set_color_primaries(nclx, nclx_colour_primaries); - if (error.code) { - std::cerr << "Invalid color primaries specified.\n"; - exit(5); - } - nclx->full_range_flag = (uint8_t) nclx_full_range; - heif_encoder_set_lossy_quality(encoder, quality); } @@ -921,48 +1280,41 @@ int main(int argc, char** argv) options->color_conversion_options.only_use_preferred_chroma_algorithm = true; } - 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"; - heif_encoder_release(encoder); - return 1; - } - - std::cerr << "Warning: option --even-size/-E is deprecated as it is not needed anymore.\n"; + if (premultiplied_alpha) { + heif_image_set_premultiplied_alpha(image.get(), premultiplied_alpha); + } - int right = heif_image_get_primary_width(image.get()) % 2; - int bottom = heif_image_get_primary_height(image.get()) % 2; + struct heif_image_handle* handle; - error = heif_image_crop(image.get(), 0, right, 0, bottom); + if (use_tiling) { + handle = encode_tiled(context.get(), encoder, options, output_bit_depth, *tile_generator, tiling); + } + else { + error = heif_context_encode_image(context.get(), + image.get(), + encoder, + options, + &handle); if (error.code != 0) { heif_encoding_options_free(options); heif_nclx_color_profile_free(nclx); heif_encoder_release(encoder); - std::cerr << "Could not crop image: " << error.message << "\n"; + std::cerr << "Could not encode HEIF/AVIF file: " << error.message << "\n"; return 1; } } - if (premultiplied_alpha) { - heif_image_set_premultiplied_alpha(image.get(), premultiplied_alpha); + if (handle==nullptr) { + std::cerr << "Could not encode image\n"; + return 1; } - - 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); - heif_nclx_color_profile_free(nclx); - heif_encoder_release(encoder); - std::cerr << "Could not encode HEIF/AVIF file: " << error.message << "\n"; - return 1; + if (is_primary_image) { + heif_context_set_primary_image(context.get(), handle); } + encoded_image_ids.push_back(heif_image_handle_get_item_id(handle)); + // 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. @@ -1029,6 +1381,8 @@ int main(int argc, char** argv) heif_image_handle_release(handle); heif_encoding_options_free(options); heif_nclx_color_profile_free(nclx); + + is_primary_image = false; } heif_encoder_release(encoder); @@ -1057,6 +1411,16 @@ int main(int argc, char** argv) heif_image_handle_release(primary_image_handle); } +#if WITH_EXPERIMENTAL_FEATURES + if (add_pyramid_group && encoded_image_ids.size() > 1) { + error = heif_context_add_pyramid_entity_group(context.get(), 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 + error = heif_context_write_to_file(context.get(), output_filename.c_str()); if (error.code) { std::cerr << error.message << "\n"; diff --git a/examples/heif_info.cc b/examples/heif_info.cc index cf8dae2bc4..d351db8d63 100644 --- a/examples/heif_info.cc +++ b/examples/heif_info.cc @@ -204,6 +204,12 @@ int main(int argc, char** argv) heif_free_list_of_compatible_brands(brands); } } + else { + if (errno == ENOENT) { + std::cerr << "Input file does not exist.\n"; + exit(10); + } + } } // ============================================================================== @@ -218,16 +224,16 @@ int main(int argc, char** argv) struct 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 - return 0; - } - if (err.code != 0) { std::cerr << "Could not read HEIF/AVIF file: " << err.message << "\n"; return 1; } + if (dump_boxes) { + heif_context_debug_dump_boxes_to_file(ctx.get(), STDOUT_FILENO); // dump to stdout + return 0; + } + // ============================================================================== @@ -253,6 +259,16 @@ 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 << err.message << "\n"; + return 10; + } + if (tiling.num_columns > 0) { + 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; @@ -273,6 +289,9 @@ int main(int argc, char** argv) case heif_colorspace_monochrome: printf("monochrome"); break; + case heif_colorspace_nonvisual: + printf("non-visual"); + break; default: printf("unknown"); break; @@ -666,54 +685,36 @@ int main(int argc, char** argv) } - struct heif_image* image; - err = heif_decode_image(handle, - &image, - heif_colorspace_undefined, - heif_chroma_undefined, - nullptr); - if (err.code) { - heif_image_handle_release(handle); - std::cerr << "Could not decode image " << err.message << "\n"; - return 1; + 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"; } - if (image) { - uint32_t aspect_h, aspect_v; - heif_image_get_pixel_aspect_ratio(image, &aspect_h, &aspect_v); - if (aspect_h != aspect_v) { - std::cout << "pixel aspect ratio: " << aspect_h << "/" << aspect_v << "\n"; - } - - if (heif_image_has_content_light_level(image)) { - struct heif_content_light_level clli{}; - heif_image_get_content_light_level(image, &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"; - } + struct 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"; + } - if (heif_image_has_mastering_display_colour_volume(image)) { - struct heif_mastering_display_colour_volume mdcv; - heif_image_get_mastering_display_colour_volume(image, &mdcv); + struct heif_mastering_display_colour_volume mdcv; + if (heif_image_handle_get_mastering_display_colour_volume(handle, &mdcv)) { - struct heif_decoded_mastering_display_colour_volume decoded_mdcv; - err = heif_mastering_display_colour_volume_decode(&mdcv, &decoded_mdcv); + struct 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 << "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"; - } + 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"; } - heif_image_release(image); - heif_image_handle_release(handle); } diff --git a/examples/heif_thumbnailer.cc b/examples/heif_thumbnailer.cc index 0e53c4d434..d553c28b9c 100644 --- a/examples/heif_thumbnailer.cc +++ b/examples/heif_thumbnailer.cc @@ -31,13 +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) 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 2722ce90ff..a1d5055260 100644 --- a/extra/getopt_long.c +++ b/extra/getopt_long.c @@ -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; diff --git a/fuzzing/box_fuzzer.cc b/fuzzing/box_fuzzer.cc index f47e9ae111..3bc5d24d01 100644 --- a/fuzzing/box_fuzzer.cc +++ b/fuzzing/box_fuzzer.cc @@ -31,7 +31,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) BitstreamRange range(reader, size); for (;;) { std::shared_ptr box; - Error error = Box::read(range, &box); + Error error = Box::read(range, &box, heif_get_global_security_limits()); if (error != Error::Ok || range.error()) { break; } diff --git a/fuzzing/color_conversion_fuzzer.cc b/fuzzing/color_conversion_fuzzer.cc index 3de50ffeaa..d73ccaf58f 100644 --- a/fuzzing/color_conversion_fuzzer.cc +++ b/fuzzing/color_conversion_fuzzer.cc @@ -59,7 +59,7 @@ static bool is_valid_colorspace(uint8_t colorspace) static bool read_plane(BitstreamRange* range, std::shared_ptr image, heif_channel channel, - int width, int height, int bit_depth) + uint32_t width, uint32_t height, int bit_depth) { if (width <= 0 || height <= 0) { return false; @@ -73,11 +73,11 @@ static bool read_plane(BitstreamRange* range, if (!image->add_plane(channel, width, height, bit_depth)) { return false; } - int stride; + uint32_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; @@ -85,7 +85,7 @@ static bool read_plane(BitstreamRange* range, static bool read_plane_interleaved(BitstreamRange* range, std::shared_ptr image, heif_channel channel, - int width, int height, int bit_depth, int comps) + uint32_t width, uint32_t height, int bit_depth, int comps) { if (width <= 0 || height <= 0) { return false; @@ -99,11 +99,11 @@ static bool read_plane_interleaved(BitstreamRange* range, if (!image->add_plane(channel, width, height, bit_depth)) { return false; } - int stride; + uint32_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; @@ -114,8 +114,8 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t 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; 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/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/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/go/heif/heif.go b/go/heif/heif.go index 71e531e967..4bbb258339 100644 --- a/go/heif/heif.go +++ b/go/heif/heif.go @@ -99,6 +99,7 @@ const ( ColorspaceYCbCr = C.heif_colorspace_YCbCr ColorspaceRGB = C.heif_colorspace_RGB ColorspaceMonochrome = C.heif_colorspace_monochrome + ColorspaceNonvisual = C.heif_colorspace_nonvisual ) type Channel C.enum_heif_channel @@ -112,6 +113,9 @@ 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 ) type ProgressStep C.enum_heif_progress_step @@ -235,6 +239,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 @@ -269,6 +275,8 @@ const ( 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. @@ -276,6 +284,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. @@ -321,6 +331,8 @@ const ( SuberrorNoVcCBox = C.heif_suberror_No_vvcC_box + SuberrorNoIcbrBox = C.heif_suberror_No_icbr_box + // --- Unsupported_feature --- // Image was coded with an unsupported compression method. @@ -331,6 +343,10 @@ 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 diff --git a/heifio/CMakeLists.txt b/heifio/CMakeLists.txt new file mode 100644 index 0000000000..920eeca5fa --- /dev/null +++ b/heifio/CMakeLists.txt @@ -0,0 +1,97 @@ +# 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_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) +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") +else () + message("TIFF: ------ (libtiff not found)") +endif () +message("") + diff --git a/examples/decoder.h b/heifio/decoder.h similarity index 100% rename from examples/decoder.h rename to heifio/decoder.h diff --git a/examples/decoder_jpeg.cc b/heifio/decoder_jpeg.cc similarity index 96% rename from examples/decoder_jpeg.cc rename to heifio/decoder_jpeg.cc index 2297d4dc4d..576deb955e 100644 --- a/examples/decoder_jpeg.cc +++ b/heifio/decoder_jpeg.cc @@ -54,6 +54,7 @@ extern "C" { #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) { @@ -223,9 +224,8 @@ bool ReadEXIFFromJPEG(j_decompress_ptr cinfo, #endif -InputImage loadJPEG(const char* filename) +heif_error loadJPEG(const char *filename, InputImage *input_image) { - InputImage img; struct heif_image* image = nullptr; @@ -245,8 +245,11 @@ InputImage loadJPEG(const char* filename) FILE* infile; if ((infile = fopen(filename, "rb")) == NULL) { - std::cerr << "Can't open " << filename << "\n"; - exit(1); + struct heif_error err = { + .code = heif_error_Invalid_input, + .subcode = heif_suberror_Unspecified, + .message = "Cannot open JPEG file"}; + return err; } @@ -267,13 +270,13 @@ InputImage loadJPEG(const char* filename) bool embeddedIccFlag = ReadICCProfileFromJPEG(&cinfo, &iccBuffer, &iccLen); bool embeddedXMPFlag = ReadXMPFromJPEG(&cinfo, xmpData); if (embeddedXMPFlag) { - img.xmp = xmpData; + input_image->xmp = xmpData; } bool embeddedEXIFFlag = ReadEXIFFromJPEG(&cinfo, exifData); if (embeddedEXIFFlag) { - img.exif = exifData; - img.orientation = (heif_orientation) read_exif_orientation_tag(exifData.data(), (int) exifData.size()); + 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) { @@ -484,8 +487,8 @@ InputImage loadJPEG(const char* filename) fclose(infile); - img.image = std::shared_ptr(image, + input_image->image = std::shared_ptr(image, [](heif_image* img) { heif_image_release(img); }); - return img; + return heif_error_ok; } diff --git a/examples/decoder_jpeg.h b/heifio/decoder_jpeg.h similarity index 94% rename from examples/decoder_jpeg.h rename to heifio/decoder_jpeg.h index 69261c3ab6..fd717604d9 100644 --- a/examples/decoder_jpeg.h +++ b/heifio/decoder_jpeg.h @@ -29,6 +29,7 @@ #include "decoder.h" -InputImage loadJPEG(const char* filename); +LIBHEIF_API +heif_error loadJPEG(const char *filename, InputImage *input_image); #endif //LIBHEIF_DECODER_JPEG_H diff --git a/examples/decoder_png.cc b/heifio/decoder_png.cc similarity index 89% rename from examples/decoder_png.cc rename to heifio/decoder_png.cc index 9d30329b02..21b3c37bf6 100644 --- a/examples/decoder_png.cc +++ b/heifio/decoder_png.cc @@ -36,6 +36,8 @@ 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) { @@ -45,17 +47,17 @@ user_read_fn(png_structp png_ptr, png_bytep data, png_size_t length) } // user_read_data -InputImage loadPNG(const char* filename, int output_bit_depth) +heif_error loadPNG(const char* filename, int output_bit_depth, InputImage *input_image) { FILE* fh = fopen(filename, "rb"); if (!fh) { - std::cerr << "Can't open " << filename << "\n"; - exit(1); + struct heif_error err = { + .code = heif_error_Invalid_input, + .subcode = heif_suberror_Unspecified, + .message = "Cannot open PNG file"}; + return err; } - - InputImage input_image; - // ### Code copied from LibVideoGfx and slightly modified to use HeifPixelImage struct heif_image* image = nullptr; @@ -203,11 +205,11 @@ InputImage loadPNG(const char* filename, int output_bit_depth) 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); + 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); + modify_exif_orientation_tag_if_it_exists(input_image->exif.data(), (int) input_image->exif.size(), 1); } #endif @@ -227,8 +229,8 @@ InputImage loadPNG(const char* filename, int output_bit_depth) // TODO: error } else { - input_image.xmp.resize(textLength); - memcpy(input_image.xmp.data(), textPtr->text, textLength); + input_image->xmp.resize(textLength); + memcpy(input_image->xmp.data(), textPtr->text, textLength); } } } @@ -331,6 +333,29 @@ InputImage loadPNG(const char* filename, int output_bit_depth) } } } + 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); + + int stride; + uint8_t* p = heif_image_get_plane(image, heif_channel_Y, &stride); + + int strideA; + uint8_t* pA = heif_image_get_plane(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, @@ -421,8 +446,8 @@ InputImage loadPNG(const char* filename, int output_bit_depth) delete[] row_pointers; fclose(fh); - input_image.image = std::shared_ptr(image, + input_image->image = std::shared_ptr(image, [](heif_image* img) { heif_image_release(img); }); - return input_image; + return heif_error_ok; } diff --git a/examples/decoder_png.h b/heifio/decoder_png.h similarity index 92% rename from examples/decoder_png.h rename to heifio/decoder_png.h index efc1171ba5..811df0d5be 100644 --- a/examples/decoder_png.h +++ b/heifio/decoder_png.h @@ -29,6 +29,7 @@ #include "decoder.h" -InputImage loadPNG(const char* filename, int output_bit_depth); +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_tiff.cc b/heifio/decoder_tiff.cc new file mode 100644 index 0000000000..524e30ddb0 --- /dev/null +++ b/heifio/decoder_tiff.cc @@ -0,0 +1,492 @@ +/* + 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. +*/ + +#include +#include +#include +#include +#include + +extern "C" { +#include +#include +} + +#include "decoder_tiff.h" + +static struct heif_error heif_error_ok = {heif_error_Ok, heif_suberror_Unspecified, "Success"}; + +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); + } +} + +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; + } + return heif_error_ok; +} + +heif_error readMono(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_monochrome, heif_chroma_monochrome, image); + if (err.code != heif_error_Ok) { + return err; + } + heif_image_add_plane(*image, heif_channel_Y, (int)width, (int)height, 8); + + int y_stride; + uint8_t *py = heif_image_get_plane(*image, heif_channel_Y, &y_stride); + for (uint32_t row = 0; row < height; row++) + { + TIFFReadScanline(tif, py, row, 0); + py += y_stride; + } + return heif_error_ok; +} + +heif_error readPixelInterleaveRGB(TIFF *tif, uint16_t samplesPerPixel, heif_image **image) +{ + uint32_t width, height; + heif_error err = getImageWidthAndHeight(tif, width, height); + if (err.code != heif_error_Ok) { + return err; + } + heif_chroma chroma = heif_chroma_interleaved_RGB; + if (samplesPerPixel == 4) { + chroma = heif_chroma_interleaved_RGBA; + } + + 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, samplesPerPixel * 8); + + int y_stride; + uint8_t *py = heif_image_get_plane(*image, channel, &y_stride); + + tdata_t buf = _TIFFmalloc(TIFFScanlineSize(tif)); + for (uint32_t row = 0; row < height; row++) + { + TIFFReadScanline(tif, buf, row, 0); + memcpy(py, buf, width * samplesPerPixel); + py += y_stride; + } + _TIFFfree(buf); + return heif_error_ok; +} + +heif_error readPixelInterleave(TIFF *tif, uint16_t samplesPerPixel, heif_image **image) +{ + if (samplesPerPixel == 1) { + return readMono(tif, image); + } else { + return readPixelInterleaveRGB(tif, samplesPerPixel, image); + } +} + +heif_error readBandInterleaveRGB(TIFF *tif, uint16_t samplesPerPixel, heif_image **image) +{ + uint32_t width, height; + heif_error err = getImageWidthAndHeight(tif, width, height); + if (err.code != heif_error_Ok) { + return err; + } + if (samplesPerPixel == 3) { + err = heif_image_create((int)width, (int)height, heif_colorspace_RGB, heif_chroma_interleaved_RGB, image); + } else { + err = heif_image_create((int)width, (int)height, heif_colorspace_RGB, heif_chroma_interleaved_RGBA, 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, samplesPerPixel * 8); + + int y_stride; + uint8_t *py = heif_image_get_plane(*image, channel, &y_stride); + + uint8_t *buf = static_cast(_TIFFmalloc(TIFFScanlineSize(tif))); + for (uint16_t i = 0; i < samplesPerPixel; i++) + { + uint8_t *dest = py + i; + for (uint32_t row = 0; row < height; row++) + { + TIFFReadScanline(tif, buf, row, i); + for (uint32_t x = 0; x < width; x++, dest += samplesPerPixel) + { + *dest = buf[x]; + } + dest += (y_stride - width * samplesPerPixel); + } + } + _TIFFfree(buf); + return heif_error_ok; +} + + +heif_error readBandInterleave(TIFF *tif, uint16_t samplesPerPixel, heif_image **image) +{ + if (samplesPerPixel == 1) { + return readMono(tif, image); + } else if (samplesPerPixel == 3) { + return readBandInterleaveRGB(tif, samplesPerPixel, image); + } else if (samplesPerPixel == 4) { + return readBandInterleaveRGB(tif, samplesPerPixel, 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; + } +} + + +static void suppress_warnings(const char* module, const char* fmt, va_list ap) { + // Do nothing +} + + +heif_error loadTIFF(const char* filename, InputImage *input_image) { + TIFFSetWarningHandler(suppress_warnings); + + std::unique_ptr tifPtr(TIFFOpen(filename, "r"), [](TIFF* tif) { TIFFClose(tif); }); + if (!tifPtr) { + struct heif_error err = { + .code = heif_error_Invalid_input, + .subcode = heif_suberror_Unspecified, + .message = "Cannot open TIFF ile"}; + return err; + } + + TIFF* tif = tifPtr.get(); + if (TIFFIsTiled(tif)) { + struct heif_error err = { + .code = heif_error_Unsupported_feature, + .subcode = heif_suberror_Unspecified, + .message = "Tiled TIFF images are not supported yet"}; + return err; + } + + uint16_t shortv, samplesPerPixel, bps, config, format; + if (TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &shortv) && shortv == PHOTOMETRIC_PALETTE) { + struct heif_error err = { + .code = heif_error_Unsupported_feature, + .subcode = heif_suberror_Unspecified, + .message = "Palette TIFF images are not supported yet"}; + return err; + } + + TIFFGetField(tif, TIFFTAG_PLANARCONFIG, &config); + TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &samplesPerPixel); + if (samplesPerPixel != 1 && samplesPerPixel != 3 && samplesPerPixel != 4) { + struct heif_error err = { + .code = heif_error_Invalid_input, + .subcode = heif_suberror_Unspecified, + .message = "Only 1, 3 and 4 samples per pixel are supported."}; + return err; + } + + TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bps); + if (bps != 8) { + struct heif_error err = { + .code = heif_error_Invalid_input, + .subcode = heif_suberror_Unspecified, + .message = "Only 8 bits per sample are supported."}; + return err; + } + + if (TIFFGetField(tif, TIFFTAG_SAMPLEFORMAT, &format) && format != SAMPLEFORMAT_UINT) { + struct heif_error err = { + .code = heif_error_Invalid_input, + .subcode = heif_suberror_Unspecified, + .message = "Only UINT sample format is supported."}; + return err; + } + + struct heif_error err; + struct heif_image* image = nullptr; + + switch (config) { + case PLANARCONFIG_CONTIG: + err = readPixelInterleave(tif, samplesPerPixel, &image); + break; + case PLANARCONFIG_SEPARATE: + err = readBandInterleave(tif, samplesPerPixel, &image); + break; + default: + struct heif_error err = { + .code = heif_error_Invalid_input, + .subcode = heif_suberror_Unspecified, + .message = "Unsupported planar configuration"}; + return err; + } + 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; +} + diff --git a/heifio/decoder_tiff.h b/heifio/decoder_tiff.h new file mode 100644 index 0000000000..81d04e2b7a --- /dev/null +++ b/heifio/decoder_tiff.h @@ -0,0 +1,36 @@ +/* + 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" + +LIBHEIF_API +heif_error loadTIFF(const char *filename, InputImage *input_image); + +#endif // LIBHEIF_DECODER_TIFF_H diff --git a/examples/decoder_y4m.cc b/heifio/decoder_y4m.cc similarity index 70% rename from examples/decoder_y4m.cc rename to heifio/decoder_y4m.cc index 7eb7b3a604..024212f0fd 100644 --- a/examples/decoder_y4m.cc +++ b/heifio/decoder_y4m.cc @@ -30,10 +30,10 @@ #include #include +static struct heif_error heif_error_ok = {heif_error_Ok, heif_suberror_Unspecified, "Success"}; -InputImage loadY4M(const char* filename) +heif_error loadY4M(const char *filename, InputImage *input_image) { - InputImage inputimage; struct heif_image* image = nullptr; @@ -42,8 +42,11 @@ InputImage loadY4M(const char* filename) std::ifstream istr(filename, std::ios_base::binary); if (istr.fail()) { - std::cerr << "Can't open " << filename << "\n"; - exit(1); + struct heif_error err = { + .code = heif_error_Invalid_input, + .subcode = heif_suberror_Unspecified, + .message = "Cannot open Y4M file"}; + return err; } @@ -51,8 +54,11 @@ InputImage loadY4M(const char* filename) getline(istr, header); if (header.find("YUV4MPEG2 ") != 0) { - std::cerr << "Input is not a Y4M file.\n"; - exit(1); + 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; @@ -71,8 +77,11 @@ InputImage loadY4M(const char* filename) } if (end - pos <= 1) { - std::cerr << "Header format error in Y4M file.\n"; - exit(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]; @@ -89,21 +98,28 @@ InputImage loadY4M(const char* filename) getline(istr, frameheader); if (frameheader != "FRAME") { - std::cerr << "Y4M misses the frame header.\n"; - exit(1); + 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) { - std::cerr << "Y4M has invalid frame size.\n"; - exit(1); + 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); - (void) err; - // TODO: handle error + 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); @@ -126,8 +142,8 @@ InputImage loadY4M(const char* filename) istr.read((char*) (pcr + y * cr_stride), (w + 1) / 2); } - inputimage.image = std::shared_ptr(image, - [](heif_image* img) { heif_image_release(img); }); + input_image->image = std::shared_ptr(image, + [](heif_image* img) { heif_image_release(img); }); - return inputimage; + return heif_error_ok; } diff --git a/examples/decoder_y4m.h b/heifio/decoder_y4m.h similarity index 89% rename from examples/decoder_y4m.h rename to heifio/decoder_y4m.h index 8f9ace46a8..91a7a5aadf 100644 --- a/examples/decoder_y4m.h +++ b/heifio/decoder_y4m.h @@ -29,6 +29,15 @@ #include "decoder.h" -InputImage loadY4M(const char* filename); +#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 96% rename from examples/encoder.cc rename to heifio/encoder.cc index 0705341e76..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 diff --git a/examples/encoder.h b/heifio/encoder.h similarity index 96% rename from examples/encoder.h rename to heifio/encoder.h index eff9ba8adc..c2a176582a 100644 --- a/examples/encoder.h +++ b/heifio/encoder.h @@ -1,5 +1,5 @@ /* - libheif example application "convert". + libheif example application. MIT License @@ -30,7 +30,7 @@ #include #include -#include +#include "libheif/heif.h" #include diff --git a/examples/encoder_jpeg.cc b/heifio/encoder_jpeg.cc similarity index 96% rename from examples/encoder_jpeg.cc rename to heifio/encoder_jpeg.cc index 529ab9409d..369f9bfae4 100644 --- a/examples/encoder_jpeg.cc +++ b/heifio/encoder_jpeg.cc @@ -1,5 +1,5 @@ /* - libheif example application "convert". + libheif example application. MIT License @@ -24,9 +24,8 @@ SOFTWARE. */ -#include -#include -#include +#include +#include #include #include @@ -34,10 +33,20 @@ #include "encoder_jpeg.h" #include "exif.h" +#include + #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) { if (quality_ < 0 || quality_ > 100) { @@ -48,15 +57,11 @@ 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 = 0; - } - 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); @@ -147,7 +152,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); @@ -201,6 +206,7 @@ bool JpegEncoder::Encode(const struct heif_image_handle* handle, // 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) { diff --git a/examples/encoder_jpeg.h b/heifio/encoder_jpeg.h similarity index 89% rename from examples/encoder_jpeg.h rename to heifio/encoder_jpeg.h index fb4e4fccc3..076515d148 100644 --- a/examples/encoder_jpeg.h +++ b/heifio/encoder_jpeg.h @@ -1,5 +1,5 @@ /* - libheif example application "convert". + libheif example application. MIT License @@ -31,8 +31,6 @@ #include #include -#include - #include #include "encoder.h" @@ -61,14 +59,6 @@ class JpegEncoder : public Encoder 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/examples/encoder_png.cc b/heifio/encoder_png.cc similarity index 98% rename from examples/encoder_png.cc rename to heifio/encoder_png.cc index f89438a5c7..8af4fa8d39 100644 --- a/examples/encoder_png.cc +++ b/heifio/encoder_png.cc @@ -1,5 +1,5 @@ /* - libheif example application "convert". + libheif example application. MIT License @@ -124,6 +124,7 @@ bool PngEncoder::Encode(const struct heif_image_handle* handle, // 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); } diff --git a/examples/encoder_png.h b/heifio/encoder_png.h similarity index 97% rename from examples/encoder_png.h rename to heifio/encoder_png.h index 7b3a25e8a3..7ffb83d536 100644 --- a/examples/encoder_png.h +++ b/heifio/encoder_png.h @@ -1,5 +1,5 @@ /* - libheif example application "convert". + libheif example application. MIT License @@ -50,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 diff --git a/heifio/encoder_tiff.cc b/heifio/encoder_tiff.cc new file mode 100644 index 0000000000..490fc18b45 --- /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); + + int stride_rgb; + const uint8_t *row_rgb = heif_image_get_plane_readonly(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..dc83e44f02 --- /dev/null +++ b/heifio/encoder_tiff.h @@ -0,0 +1,64 @@ +/* + 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 Encode(const struct heif_image_handle* handle, + const struct 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 95% rename from examples/encoder_y4m.cc rename to heifio/encoder_y4m.cc index a244ffb50a..79eb2e33a9 100644 --- a/examples/encoder_y4m.cc +++ b/heifio/encoder_y4m.cc @@ -1,5 +1,5 @@ /* - libheif example application "convert". + libheif example application. MIT License @@ -25,9 +25,9 @@ */ #include "encoder_y4m.h" -#include -#include -#include +#include +#include +#include Y4MEncoder::Y4MEncoder() = default; @@ -36,6 +36,7 @@ Y4MEncoder::Y4MEncoder() = default; void Y4MEncoder::UpdateDecodingOptions(const struct heif_image_handle* handle, struct heif_decoding_options* options) const { + options->convert_hdr_to_8bit = 1; } diff --git a/examples/encoder_y4m.h b/heifio/encoder_y4m.h similarity index 97% rename from examples/encoder_y4m.h rename to heifio/encoder_y4m.h index e1e1ba659d..b3e259590a 100644 --- a/examples/encoder_y4m.h +++ b/heifio/encoder_y4m.h @@ -1,5 +1,5 @@ /* - libheif example application "convert". + libheif example application. MIT License diff --git a/libheif/exif.cc b/heifio/exif.cc similarity index 54% rename from libheif/exif.cc rename to heifio/exif.cc index 82c3cd1328..33d55f2c6e 100644 --- a/libheif/exif.cc +++ b/heifio/exif.cc @@ -22,8 +22,14 @@ #include "exif.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. @@ -74,38 +80,62 @@ static void write16(uint8_t* data, uint32_t size, uint32_t pos, uint16_t value, } } -// 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) + +static void write32(uint8_t* data, uint32_t size, uint32_t pos, uint32_t value, bool littleEndian) { - if (size < 4) { - return 0; + 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); } +} - if ((exif[0] != 'I' && exif[0] != 'M') || - (exif[1] != 'I' && exif[1] != 'M')) { + +// 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; } - bool littleEndian = (exif[0] == 'I'); - - assert(out_littleEndian); - *out_littleEndian = littleEndian; + uint32_t offset = ifd_offset; - uint32_t offset = read32(exif, size, 4, littleEndian); + // 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 - 2 < offset) { + 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 to prevent an underflow in the following statement. - if (2U + cnt * 12U > size) { + // 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 - if (size - 2U - cnt * 12U > offset) { + // offset + IFD_table_size > size ? + if (size - IFD_table_size < offset) { return 0; } @@ -114,11 +144,78 @@ static uint32_t find_exif_tag(const uint8_t* exif, uint32_t size, uint16_t query 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; + } + } } - // TODO: do we have to also scan the next IFD table ? + // continue with next IFD table - return 0; + 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); + } + } } diff --git a/libheif/exif.h b/heifio/exif.h similarity index 90% rename from libheif/exif.h rename to heifio/exif.h index 7598c65534..2a4de22e89 100644 --- a/libheif/exif.h +++ b/heifio/exif.h @@ -28,4 +28,6 @@ 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/CMakeLists.txt b/libheif/CMakeLists.txt index 4555cabc21..041f776001 100644 --- a/libheif/CMakeLists.txt +++ b/libheif/CMakeLists.txt @@ -22,19 +22,23 @@ set(libheif_sources 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 - metadata_compression.cc - metadata_compression.h + compression.h + compression_brotli.cc + compression_zlib.cc common_utils.cc common_utils.h region.cc @@ -45,22 +49,56 @@ set(libheif_sources api/libheif/heif_plugin.cc api/libheif/heif_properties.cc api/libheif/heif_items.cc - api/libheif/heif_experimental.h - api/libheif/heif_experimental.cc - codecs/hevc.cc - codecs/hevc.h - codecs/avif.cc - codecs/avif.h - codecs/jpeg.h - codecs/jpeg.cc - codecs/jpeg2000.h - codecs/jpeg2000.cc - codecs/vvc.h - codecs/vvc.cc - codecs/avc.h - codecs/avc.cc - codecs/mask_image.cc - codecs/mask_image.h + codecs/decoder.h + codecs/decoder.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 + image-items/avif.cc + image-items/avif.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 + image-items/jpeg2000.h + image-items/jpeg2000.cc + codecs/jpeg2000_dec.h + codecs/jpeg2000_dec.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_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 + 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 @@ -91,6 +129,16 @@ if (ENABLE_PLUGIN_LOADING) endif () endif () +option(WITH_EXPERIMENTAL_FEATURES "Compile experimental features and install headers with unstable API" OFF) +if (WITH_EXPERIMENTAL_FEATURES) + list(APPEND libheif_sources api/libheif/heif_experimental.h + api/libheif/heif_experimental.cc) + + list(APPEND libheif_headers api/libheif/heif_experimental.h) + + target_compile_definitions(heif PUBLIC WITH_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}/libheif ${libheif_SOURCE_DIR}/libheif/api) @@ -133,12 +181,17 @@ else () message("Not compiling 'libsharpyuv'") endif () -if (WITH_DEFLATE_HEADER_COMPRESSION) - find_package(ZLIB REQUIRED) +if (ZLIB_FOUND) + target_compile_definitions(heif PRIVATE HAVE_ZLIB=1) target_link_libraries(heif PRIVATE ZLIB::ZLIB) - target_compile_definitions(heif PRIVATE WITH_DEFLATE_HEADER_COMPRESSION=1) endif () +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 (ENABLE_MULTITHREADING_SUPPORT) find_package(Threads) target_link_libraries(heif PRIVATE ${CMAKE_THREAD_LIBS_INIT}) @@ -152,10 +205,26 @@ endif () if (WITH_UNCOMPRESSED_CODEC) target_compile_definitions(heif PUBLIC WITH_UNCOMPRESSED_CODEC=1) target_sources(heif PRIVATE - codecs/uncompressed_box.h - codecs/uncompressed_box.cc - codecs/uncompressed_image.h - codecs/uncompressed_image.cc) + 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/decoder_abstract.h + codecs/uncompressed/decoder_abstract.cc + codecs/uncompressed/decoder_component_interleave.h + codecs/uncompressed/decoder_component_interleave.cc + codecs/uncompressed/decoder_pixel_interleave.h + codecs/uncompressed/decoder_pixel_interleave.cc + codecs/uncompressed/decoder_mixed_interleave.h + codecs/uncompressed/decoder_mixed_interleave.cc + codecs/uncompressed/decoder_row_interleave.h + codecs/uncompressed/decoder_row_interleave.cc + codecs/uncompressed/decoder_tile_component_interleave.h + codecs/uncompressed/decoder_tile_component_interleave.cc) endif () write_basic_package_version_file(${PROJECT_NAME}-config-version.cmake COMPATIBILITY ExactVersion) diff --git a/libheif/api/libheif/api_structs.h b/libheif/api/libheif/api_structs.h index 1947c409f1..77840ca8ee 100644 --- a/libheif/api/libheif/api_structs.h +++ b/libheif/api/libheif/api_structs.h @@ -25,10 +25,11 @@ #include "context.h" #include +#include "image-items/image_item.h" struct heif_image_handle { - std::shared_ptr image; + std::shared_ptr image; // store reference to keep the context alive while we are using the handle (issue #147) std::shared_ptr context; diff --git a/libheif/api/libheif/heif.cc b/libheif/api/libheif/heif.cc index d702fb7798..5cba807278 100644 --- a/libheif/api/libheif/heif.cc +++ b/libheif/api/libheif/heif.cc @@ -19,6 +19,7 @@ */ #include "heif_plugin.h" +#include "security_limits.h" #include "region.h" #include "common_utils.h" #include @@ -31,9 +32,16 @@ #include "error.h" #include "bitstream.h" #include "init.h" +#include "image-items/grid.h" +#include "image-items/overlay.h" +#include "image-items/tiled.h" #include #include +#if WITH_UNCOMPRESSED_CODEC +#include "image-items/unc_image.h" +#endif + #if defined(__EMSCRIPTEN__) && !defined(__EMSCRIPTEN_STANDALONE_WASM__) #include "heif_emscripten.h" #endif @@ -60,7 +68,7 @@ #include -const struct heif_error heif_error_success = {heif_error_Ok, heif_suberror_Unspecified, kSuccess}; +const struct heif_error heif_error_success = {heif_error_Ok, heif_suberror_Unspecified, Error::kSuccess}; static struct heif_error error_unsupported_parameter = {heif_error_Usage_error, heif_suberror_Unsupported_parameter, "Unsupported encoder parameter"}; @@ -285,13 +293,13 @@ heif_brand2 heif_read_main_brand(const uint8_t* data, int len) } -heif_brand2 heif_fourcc_to_brand(const char* fourcc) +heif_brand2 heif_fourcc_to_brand(const char* fourcc_string) { - if (fourcc == nullptr || !fourcc[0] || !fourcc[1] || !fourcc[2] || !fourcc[3]) { + if (fourcc_string == nullptr || !fourcc_string[0] || !fourcc_string[1] || !fourcc_string[2] || !fourcc_string[3]) { return 0; } - return fourcc_to_uint32(fourcc); + return fourcc(fourcc_string); } @@ -316,7 +324,7 @@ int heif_has_compatible_brand(const uint8_t* data, int len, const char* brand_fo BitstreamRange range(stream, len); std::shared_ptr box; - Error err = Box::read(range, &box); + Error err = Box::read(range, &box, heif_get_global_security_limits()); if (err) { if (err.sub_error_code == heif_suberror_End_of_data) { return -1; @@ -330,7 +338,7 @@ int heif_has_compatible_brand(const uint8_t* data, int len, const char* brand_fo return -2; } - return ftyp->has_compatible_brand(fourcc_to_uint32(brand_fourcc)) ? 1 : 0; + return ftyp->has_compatible_brand(fourcc(brand_fourcc)) ? 1 : 0; } @@ -348,7 +356,7 @@ struct heif_error heif_list_compatible_brands(const uint8_t* data, int len, heif BitstreamRange range(stream, len); std::shared_ptr box; - Error err = Box::read(range, &box); + Error err = Box::read(range, &box, heif_get_global_security_limits()); if (err) { if (err.sub_error_code == heif_suberror_End_of_data) { return {err.error_code, err.sub_error_code, "insufficient input data"}; @@ -472,6 +480,36 @@ const char* heif_get_file_mime_type(const uint8_t* data, int len) } +const struct heif_security_limits* heif_get_global_security_limits() +{ + return &global_security_limits; +} + + +struct heif_security_limits* heif_context_get_security_limits(const struct heif_context* ctx) +{ + if (!ctx) { + return nullptr; + } + + return ctx->context->get_security_limits(); +} + + +struct heif_error heif_context_set_security_limits(struct heif_context* ctx, const struct heif_security_limits* limits) +{ + if (ctx==nullptr || limits==nullptr) { + return {heif_error_Usage_error, + heif_suberror_Null_pointer_argument}; + } + + ctx->context->set_security_limits(limits); + + return heif_error_ok; +} + + + heif_context* heif_context_alloc() { load_plugins_if_not_initialized_yet(); @@ -545,7 +583,7 @@ heif_error heif_context_get_primary_image_handle(heif_context* ctx, heif_image_h return err.error_struct(ctx->context.get()); } - std::shared_ptr primary_image = ctx->context->get_primary_image(); + std::shared_ptr primary_image = ctx->context->get_primary_image(true); // It is a requirement of an HEIF file there is always a primary image. // If there is none, an error is generated when loading the file. @@ -555,6 +593,11 @@ heif_error heif_context_get_primary_image_handle(heif_context* ctx, heif_image_h return err.error_struct(ctx->context.get()); } + if (auto errImage = std::dynamic_pointer_cast(primary_image)) { + Error error = errImage->get_item_error(); + return error.error_struct(ctx->context.get()); + } + *img = new heif_image_handle(); (*img)->image = std::move(primary_image); (*img)->context = ctx->context; @@ -570,7 +613,7 @@ struct heif_error heif_context_get_primary_image_ID(struct heif_context* ctx, he heif_suberror_Null_pointer_argument).error_struct(ctx->context.get()); } - std::shared_ptr primary = ctx->context->get_primary_image(); + std::shared_ptr primary = ctx->context->get_primary_image(true); if (!primary) { return Error(heif_error_Invalid_input, heif_suberror_No_or_invalid_primary_item).error_struct(ctx->context.get()); @@ -584,7 +627,7 @@ struct heif_error heif_context_get_primary_image_ID(struct heif_context* ctx, he int heif_context_is_top_level_image_ID(struct heif_context* ctx, heif_item_id id) { - const std::vector> images = ctx->context->get_top_level_images(); + const std::vector> images = ctx->context->get_top_level_images(true); for (const auto& img : images) { if (img->get_id() == id) { @@ -598,7 +641,7 @@ int heif_context_is_top_level_image_ID(struct heif_context* ctx, heif_item_id id int heif_context_get_number_of_top_level_images(heif_context* ctx) { - return (int) ctx->context->get_top_level_images().size(); + return (int) ctx->context->get_top_level_images(true).size(); } @@ -613,7 +656,7 @@ int heif_context_get_list_of_top_level_image_IDs(struct heif_context* ctx, // fill in ID values into output array - const std::vector> imgs = ctx->context->get_top_level_images(); + const std::vector> imgs = ctx->context->get_top_level_images(true); int n = (int) std::min(count, (int) imgs.size()); for (int i = 0; i < n; i++) { ID_array[i] = imgs[i]->get_id(); @@ -631,7 +674,12 @@ struct heif_error heif_context_get_image_handle(struct heif_context* ctx, return {heif_error_Usage_error, heif_suberror_Null_pointer_argument, ""}; } - auto image = ctx->context->get_image(id); + auto image = ctx->context->get_image(id, true); + + if (auto errImage = std::dynamic_pointer_cast(image)) { + Error error = errImage->get_item_error(); + return error.error_struct(ctx->context.get()); + } if (!image) { *imgHdl = nullptr; @@ -853,11 +901,245 @@ struct heif_context* heif_image_handle_get_context(const struct heif_image_handl } +heif_error heif_image_handle_get_image_tiling(const struct heif_image_handle* handle, int process_image_transformations, struct heif_image_tiling* tiling) +{ + if (!handle || !tiling) { + return {heif_error_Usage_error, + heif_suberror_Null_pointer_argument, + "NULL passed to heif_image_handle_get_image_tiling()"}; + } + + *tiling = handle->image->get_heif_image_tiling(); + + if (process_image_transformations) { + Error error = handle->image->process_image_transformations_on_tiling(*tiling); + if (error) { + return error.error_struct(handle->context.get()); + } + } + + return heif_error_ok; +} + + +struct heif_error heif_image_handle_get_grid_image_tile_id(const struct heif_image_handle* handle, + int process_image_transformations, + uint32_t tile_x, uint32_t tile_y, + heif_item_id* tile_item_id) +{ + if (!handle || !tile_item_id) { + return { heif_error_Usage_error, + heif_suberror_Null_pointer_argument }; + } + + std::shared_ptr gridItem = std::dynamic_pointer_cast(handle->image); + if (!gridItem) { + return { heif_error_Usage_error, + heif_suberror_Unspecified, + "Image is no grid image" }; + } + + const ImageGrid& gridspec = gridItem->get_grid_spec(); + if (tile_x >= gridspec.get_columns() || tile_y >= gridspec.get_rows()) { + return { heif_error_Usage_error, + heif_suberror_Unspecified, + "Grid tile index out of range" }; + } + + if (process_image_transformations) { + gridItem->transform_requested_tile_position_to_original_tile_position(tile_x, tile_y); + } + + *tile_item_id = gridItem->get_grid_tiles()[tile_y * gridspec.get_columns() + tile_x]; + + return heif_error_ok; +} + + +#if 0 +// TODO: do we need this ? This does not handle rotations. We can use heif_image_handle_get_image_tiling() to get the same information. +struct heif_error heif_image_handle_get_tile_size(const struct heif_image_handle* handle, + uint32_t* tile_width, uint32_t* tile_height) +{ + if (!handle) { + return error_null_parameter; + } + + + uint32_t w,h; + + handle->image->get_tile_size(w,h); + + if (tile_width) { + *tile_width = w; + } + + if (tile_height) { + *tile_height = h; + } + + return heif_error_success; +} +#endif + + +struct heif_entity_group* heif_context_get_entity_groups(const struct heif_context* ctx, + uint32_t type_filter, uint32_t item_filter, + int* out_num_groups) +{ + std::shared_ptr grplBox = ctx->context->get_heif_file()->get_grpl_box(); + if (!grplBox) { + *out_num_groups = 0; + return nullptr; + } + + std::vector> all_entity_group_boxes = grplBox->get_all_child_boxes(); + if (all_entity_group_boxes.empty()) { + *out_num_groups = 0; + return nullptr; + } + + // --- filter groups + + std::vector> entity_group_boxes; + for (auto& group : all_entity_group_boxes) { + if (type_filter != 0 && group->get_short_type() != type_filter) { + continue; + } + + auto groupBox = std::dynamic_pointer_cast(group); + const std::vector& items = groupBox->get_item_ids(); + + if (item_filter != 0 && std::all_of(items.begin(), items.end(), [item_filter](heif_item_id item) { + return item != item_filter; + })) { + continue; + } + + entity_group_boxes.emplace_back(groupBox); + } + + // --- convert to C structs + + auto* groups = new heif_entity_group[entity_group_boxes.size()]; + for (size_t i = 0; i < entity_group_boxes.size(); i++) { + const auto& groupBox = entity_group_boxes[i]; + const std::vector& items = groupBox->get_item_ids(); + + groups[i].entity_group_id = groupBox->get_group_id(); + groups[i].entity_group_type = groupBox->get_short_type(); + groups[i].entities = (items.empty() ? nullptr : new heif_item_id[items.size()]); + groups[i].num_entities = static_cast(items.size()); + + if (groups[i].entities) { // avoid clang static analyzer false positive + for (size_t k = 0; k < items.size(); k++) { + groups[i].entities[k] = items[k]; + } + } + } + + *out_num_groups = static_cast(entity_group_boxes.size()); + return groups; +} + + +void heif_entity_groups_release(struct heif_entity_group* grp, int num_groups) +{ + for (int i=0;i layers(num_layers); + for (size_t i = 0; i < num_layers; i++) { + layers[i] = layer_item_ids[i]; + } + + Result result = ctx->context->add_pyramid_group(layers); + + if (result) { + if (out_group_id) { + *out_group_id = result.value; + } + return heif_error_success; + } + else { + return result.error.error_struct(ctx->context.get()); + } +} + + +struct heif_pyramid_layer_info* heif_context_get_pyramid_entity_group_info(struct heif_context* ctx, heif_entity_group_id id, int* out_num_layers) +{ + if (!out_num_layers) { + return nullptr; + } + + std::shared_ptr groupBox = ctx->context->get_heif_file()->get_entity_group(id); + if (!groupBox) { + return nullptr; + } + + const auto pymdBox = std::dynamic_pointer_cast(groupBox); + if (!pymdBox) { + return nullptr; + } + + const std::vector pymd_layers = pymdBox->get_layers(); + if (pymd_layers.empty()) { + return nullptr; + } + + auto items = pymdBox->get_item_ids(); + assert(items.size() == pymd_layers.size()); + + auto* layerInfo = new heif_pyramid_layer_info[pymd_layers.size()]; + for (size_t i=0; i(pymd_layers.size()); + + return layerInfo; +} + + +void heif_pyramid_layer_info_release(struct heif_pyramid_layer_info* infos) +{ + delete[] infos; +} + + struct heif_error heif_image_handle_get_preferred_decoding_colorspace(const struct heif_image_handle* image_handle, enum heif_colorspace* out_colorspace, enum heif_chroma* out_chroma) { - Error err = image_handle->image->get_preferred_decoding_colorspace(out_colorspace, out_chroma); + Error err = image_handle->image->get_coded_image_colorspace(out_colorspace, out_chroma); if (err) { return err.error_struct(image_handle->image.get()); } @@ -909,7 +1191,7 @@ int heif_image_handle_get_depth_image_representation_info(const struct heif_imag heif_item_id depth_image_id, const struct heif_depth_representation_info** out) { - std::shared_ptr depth_image; + std::shared_ptr depth_image; if (out) { if (handle->image->is_depth_channel()) { @@ -1020,29 +1302,51 @@ void fill_default_decoding_options(heif_decoding_options& options) } -static void copy_options(heif_decoding_options& options, const heif_decoding_options& input_options) +// overwrite the (possibly lower version) input options over the default options +static heif_decoding_options normalize_options(const heif_decoding_options* input_options) { - switch (input_options.version) { - case 5: - options.color_conversion_options = input_options.color_conversion_options; - // fallthrough - case 4: - options.decoder_id = input_options.decoder_id; - // fallthrough - case 3: - options.strict_decoding = input_options.strict_decoding; - // fallthrough - case 2: - options.convert_hdr_to_8bit = input_options.convert_hdr_to_8bit; - // fallthrough - case 1: - options.ignore_transformations = input_options.ignore_transformations; + heif_decoding_options options{}; + fill_default_decoding_options(options); - options.start_progress = input_options.start_progress; - options.on_progress = input_options.on_progress; - options.end_progress = input_options.end_progress; - options.progress_user_data = input_options.progress_user_data; + if (input_options) { + switch (input_options->version) { + case 5: + options.color_conversion_options = input_options->color_conversion_options; + // fallthrough + case 4: + options.decoder_id = input_options->decoder_id; + // fallthrough + case 3: + options.strict_decoding = input_options->strict_decoding; + // fallthrough + case 2: + options.convert_hdr_to_8bit = input_options->convert_hdr_to_8bit; + // fallthrough + case 1: + options.ignore_transformations = input_options->ignore_transformations; + + options.start_progress = input_options->start_progress; + options.on_progress = input_options->on_progress; + options.end_progress = input_options->end_progress; + options.progress_user_data = input_options->progress_user_data; + } } + + return options; +} + + +void heif_color_conversion_options_set_defaults(struct heif_color_conversion_options* options) +{ + options->version = 1; +#if HAVE_LIBSHARPYUV + options->preferred_chroma_downsampling_algorithm = heif_chroma_downsampling_sharp_yuv; +#else + options->preferred_chroma_downsampling_algorithm = heif_chroma_downsampling_average; +#endif + + options->preferred_chroma_upsampling_algorithm = heif_chroma_upsampling_bilinear; + options->only_use_preferred_chroma_algorithm = true; } @@ -1068,26 +1372,55 @@ struct heif_error heif_decode_image(const struct heif_image_handle* in_handle, heif_chroma chroma, const struct heif_decoding_options* input_options) { - std::shared_ptr img; - heif_item_id id = in_handle->image->get_id(); - heif_decoding_options dec_options; - fill_default_decoding_options(dec_options); + heif_decoding_options dec_options = normalize_options(input_options); - if (input_options != nullptr) { - // overwrite the (possibly lower version) input options over the default options - copy_options(dec_options, *input_options); + Result> decodingResult = in_handle->context->decode_image(id, + colorspace, + chroma, + dec_options, + false, 0,0); + if (decodingResult.error.error_code != heif_error_Ok) { + return decodingResult.error.error_struct(in_handle->image.get()); + } + + std::shared_ptr img = decodingResult.value; + + *out_img = new heif_image(); + (*out_img)->image = std::move(img); + + return Error::Ok.error_struct(in_handle->image.get()); +} + + +// TODO: move this into the Context. But first, we also have to move heif_decode_image() into Context. +struct heif_error heif_image_handle_decode_image_tile(const struct heif_image_handle* in_handle, + struct heif_image** out_img, + enum heif_colorspace colorspace, + enum heif_chroma chroma, + const struct heif_decoding_options* input_options, + uint32_t x0, uint32_t y0) +{ + if (!in_handle) { + return error_null_parameter; } - Error err = in_handle->context->decode_image_user(id, img, - colorspace, - chroma, - dec_options); - if (err.error_code != heif_error_Ok) { - return err.error_struct(in_handle->image.get()); + heif_item_id id = in_handle->image->get_id(); + + heif_decoding_options dec_options = normalize_options(input_options); + + Result> decodingResult = in_handle->context->decode_image(id, + colorspace, + chroma, + dec_options, + true, x0,y0); + if (decodingResult.error.error_code != heif_error_Ok) { + return decodingResult.error.error_struct(in_handle->image.get()); } + std::shared_ptr img = decodingResult.value; + *out_img = new heif_image(); (*out_img)->image = std::move(img); @@ -1166,6 +1499,16 @@ void heif_image_get_content_light_level(const struct heif_image* image, struct h } } +int heif_image_handle_get_content_light_level(const struct heif_image_handle* handle, struct heif_content_light_level* out) +{ + auto clli = handle->image->get_file()->get_property(handle->image->get_id()); + if (out && clli) { + *out = clli->clli; + } + + return clli ? 1 : 0; +} + void heif_image_set_content_light_level(const struct heif_image* image, const struct heif_content_light_level* in) { if (in == nullptr) { @@ -1186,6 +1529,16 @@ void heif_image_get_mastering_display_colour_volume(const struct heif_image* ima *out = image->image->get_mdcv(); } +int heif_image_handle_get_mastering_display_colour_volume(const struct heif_image_handle* handle, struct heif_mastering_display_colour_volume* out) +{ + auto mdcv = handle->image->get_file()->get_property(handle->image->get_id()); + if (out && mdcv) { + *out = mdcv->mdcv; + } + + return mdcv ? 1 : 0; +} + void heif_image_set_mastering_display_colour_volume(const struct heif_image* image, const struct heif_mastering_display_colour_volume* in) { if (in == nullptr) { @@ -1253,6 +1606,21 @@ void heif_image_get_pixel_aspect_ratio(const struct heif_image* image, uint32_t* image->image->get_pixel_ratio(aspect_h, aspect_v); } +int heif_image_handle_get_pixel_aspect_ratio(const struct heif_image_handle* handle, uint32_t* aspect_h, uint32_t* aspect_v) +{ + auto pasp = handle->image->get_file()->get_property(handle->image->get_id()); + if (pasp) { + *aspect_h = pasp->hSpacing; + *aspect_v = pasp->vSpacing; + return 1; + } + else { + *aspect_h = 1; + *aspect_v = 1; + return 0; + } +} + void heif_image_set_pixel_aspect_ratio(struct heif_image* image, uint32_t aspect_h, uint32_t aspect_v) { image->image->set_pixel_ratio(aspect_h, aspect_v); @@ -1281,15 +1649,26 @@ enum heif_chroma heif_image_get_chroma_format(const struct heif_image* img) } +static int uint32_to_int(uint32_t v) +{ + if (v == 0 || v > static_cast(std::numeric_limits::max())) { + return -1; + } + else { + return static_cast(v); + } +} + + int heif_image_get_width(const struct heif_image* img, enum heif_channel channel) { - return img->image->get_width(channel); + return uint32_to_int(img->image->get_width(channel)); } int heif_image_get_height(const struct heif_image* img, enum heif_channel channel) { - return img->image->get_height(channel); + return uint32_to_int(img->image->get_height(channel)); } @@ -1297,14 +1676,14 @@ int heif_image_get_primary_width(const struct heif_image* img) { if (img->image->get_colorspace() == heif_colorspace_RGB) { if (img->image->get_chroma_format() == heif_chroma_444) { - return img->image->get_width(heif_channel_G); + return uint32_to_int(img->image->get_width(heif_channel_G)); } else { - return img->image->get_width(heif_channel_interleaved); + return uint32_to_int(img->image->get_width(heif_channel_interleaved)); } } else { - return img->image->get_width(heif_channel_Y); + return uint32_to_int(img->image->get_width(heif_channel_Y)); } } @@ -1313,14 +1692,14 @@ int heif_image_get_primary_height(const struct heif_image* img) { if (img->image->get_colorspace() == heif_colorspace_RGB) { if (img->image->get_chroma_format() == heif_chroma_444) { - return img->image->get_height(heif_channel_G); + return uint32_to_int(img->image->get_height(heif_channel_G)); } else { - return img->image->get_height(heif_channel_interleaved); + return uint32_to_int(img->image->get_height(heif_channel_interleaved)); } } else { - return img->image->get_height(heif_channel_Y); + return uint32_to_int(img->image->get_height(heif_channel_Y)); } } @@ -1328,17 +1707,22 @@ int heif_image_get_primary_height(const struct heif_image* img) heif_error heif_image_crop(struct heif_image* img, int left, int right, int top, int bottom) { - std::shared_ptr out_img; + uint32_t w = img->image->get_width(); + uint32_t h = img->image->get_height(); - int w = img->image->get_width(); - int h = img->image->get_height(); + if (w==0 || w>0x7FFFFFFF || + h==0 || h>0x7FFFFFFF) { + return heif_error{heif_error_Usage_error, + heif_suberror_Invalid_image_size, + "Image size exceeds maximum supported size"}; + } - Error err = img->image->crop(left, w - 1 - right, top, h - 1 - bottom, out_img); - if (err) { - return err.error_struct(img->image.get()); + auto cropResult = img->image->crop(left, static_cast(w) - 1 - right, top, static_cast(h) - 1 - bottom); + if (cropResult.error) { + return cropResult.error.error_struct(img->image.get()); } - img->image = out_img; + img->image = cropResult.value; return heif_error_success; } @@ -1377,16 +1761,46 @@ struct heif_error heif_image_add_plane(struct heif_image* image, } +struct heif_error heif_image_add_channel(struct heif_image* image, + enum heif_channel channel, + int width, int height, + heif_channel_datatype datatype, int bit_depth) +{ + if (!image->image->add_channel(channel, width, height, datatype, bit_depth)) { + struct heif_error err = {heif_error_Memory_allocation_error, + heif_suberror_Unspecified, + "Cannot allocate memory for image plane"}; + return err; + } + else { + return heif_error_success; + } +} + + const uint8_t* heif_image_get_plane_readonly(const struct heif_image* image, enum heif_channel channel, int* out_stride) { + if (!out_stride) { + return nullptr; + } + if (!image || !image->image) { *out_stride = 0; return nullptr; } - return image->image->get_plane(channel, out_stride); + uint32_t stride; + const auto* p = image->image->get_plane(channel, &stride); + + // TODO: use C++20 std::cmp_greater() + if (stride > static_cast(std::numeric_limits::max())) { + return nullptr; + } + + *out_stride = static_cast(stride); + return p; } @@ -1394,15 +1808,115 @@ uint8_t* heif_image_get_plane(struct heif_image* image, enum heif_channel channel, int* out_stride) { + if (!out_stride) { + return nullptr; + } + if (!image || !image->image) { *out_stride = 0; return nullptr; } - return image->image->get_plane(channel, out_stride); + uint32_t stride; + uint8_t* p = image->image->get_plane(channel, &stride); + + // TODO: use C++20 std::cmp_greater() + if (stride > static_cast(std::numeric_limits::max())) { + return nullptr; + } + + *out_stride = static_cast(stride); + return p; +} + + +enum heif_channel_datatype heif_image_get_datatype(const struct heif_image* image, enum heif_channel channel) +{ + if (image == nullptr) { + return heif_channel_datatype_undefined; + } + + return image->image->get_datatype(channel); } +int heif_image_list_channels(struct heif_image* image, + enum heif_channel** out_channels) +{ + if (!image || !out_channels) { + return 0; + } + + auto channels = image->image->get_channel_set(); + + *out_channels = new heif_channel[channels.size()]; + heif_channel* p = *out_channels; + for (heif_channel c : channels) { + *p++ = c; + } + + assert(channels.size() < static_cast(std::numeric_limits::max())); + + return static_cast(channels.size()); +} + + +void heif_channel_release_list(enum heif_channel** channels) +{ + delete[] channels; +} + + + +#define heif_image_get_channel_X(name, type, datatype, bits) \ +const type* heif_image_get_channel_ ## name ## _readonly(const struct heif_image* image, \ + enum heif_channel channel, \ + uint32_t* out_stride) \ +{ \ + if (!image || !image->image) { \ + *out_stride = 0; \ + return nullptr; \ + } \ + \ + if (image->image->get_datatype(channel) != datatype) { \ + return nullptr; \ + } \ + if (image->image->get_storage_bits_per_pixel(channel) != bits) { \ + return nullptr; \ + } \ + return image->image->get_channel(channel, out_stride); \ +} \ + \ +type* heif_image_get_channel_ ## name (struct heif_image* image, \ + enum heif_channel channel, \ + uint32_t* out_stride) \ +{ \ + if (!image || !image->image) { \ + *out_stride = 0; \ + return nullptr; \ + } \ + \ + if (image->image->get_datatype(channel) != datatype) { \ + return nullptr; \ + } \ + if (image->image->get_storage_bits_per_pixel(channel) != bits) { \ + return nullptr; \ + } \ + return image->image->get_channel(channel, out_stride); \ +} + +heif_image_get_channel_X(uint16, uint16_t, heif_channel_datatype_unsigned_integer, 16) +heif_image_get_channel_X(uint32, uint32_t, heif_channel_datatype_unsigned_integer, 32) +heif_image_get_channel_X(uint64, uint64_t, heif_channel_datatype_unsigned_integer, 64) +heif_image_get_channel_X(int16, int16_t, heif_channel_datatype_signed_integer, 16) +heif_image_get_channel_X(int32, int32_t, heif_channel_datatype_signed_integer, 32) +heif_image_get_channel_X(int64, int64_t, heif_channel_datatype_signed_integer, 64) +heif_image_get_channel_X(float32, float, heif_channel_datatype_floating_point, 32) +heif_image_get_channel_X(float64, double, heif_channel_datatype_floating_point, 64) +heif_image_get_channel_X(complex32, heif_complex32, heif_channel_datatype_complex_number, 64) +heif_image_get_channel_X(complex64, heif_complex64, heif_channel_datatype_complex_number, 64) + + void heif_image_set_premultiplied_alpha(struct heif_image* image, int is_premultiplied_alpha) { @@ -1456,6 +1970,21 @@ struct heif_error heif_image_scale_image(const struct heif_image* input, return Error::Ok.error_struct(input->image.get()); } + +struct heif_error heif_image_extend_to_size_fill_with_zero(struct heif_image* image, + uint32_t width, uint32_t height) +{ + bool success = image->image->extend_to_size_with_zero(width, height); + if (!success) { + return heif_error{heif_error_Memory_allocation_error, + heif_suberror_Unspecified, + "Not enough memory to extend image size."}; + } + + return heif_error_ok; +} + + struct heif_error heif_image_set_raw_color_profile(struct heif_image* image, const char* color_profile_type_fourcc, const void* profile_data, @@ -1669,8 +2198,8 @@ static const std::set::type> struct heif_error heif_nclx_color_profile_set_color_primaries(heif_color_profile_nclx* nclx, uint16_t cp) { - if (cp < std::numeric_limits::type>::min() || - cp > std::numeric_limits::type>::max()) { + if (static_cast::type>(cp) < std::numeric_limits::type>::min() || + static_cast::type>(cp) > std::numeric_limits::type>::max()) { return Error(heif_error_Invalid_input, heif_suberror_Unknown_NCLX_color_primaries).error_struct(nullptr); } @@ -1710,8 +2239,8 @@ static const std::set::type>::min() || - tc > std::numeric_limits::type>::max()) { + if (static_cast::type>(tc) < std::numeric_limits::type>::min() || + static_cast::type>(tc) > std::numeric_limits::type>::max()) { return Error(heif_error_Invalid_input, heif_suberror_Unknown_NCLX_transfer_characteristics).error_struct(nullptr); } @@ -1747,8 +2276,8 @@ static const std::set::t struct heif_error heif_nclx_color_profile_set_matrix_coefficients(struct heif_color_profile_nclx* nclx, uint16_t mc) { - if (mc < std::numeric_limits::type>::min() || - mc > std::numeric_limits::type>::max()) { + if (static_cast::type>(mc) < std::numeric_limits::type>::min() || + static_cast::type>(mc) > std::numeric_limits::type>::max()) { return Error(heif_error_Invalid_input, heif_suberror_Unknown_NCLX_matrix_coefficients).error_struct(nullptr); } @@ -2825,7 +3354,7 @@ struct heif_error heif_context_encode_image(struct heif_context* ctx, } } - std::shared_ptr image; + std::shared_ptr image; Error error; @@ -2898,15 +3427,14 @@ struct heif_error heif_context_encode_grid(struct heif_context* ctx, } // Encode Grid - Error error; - std::shared_ptr out_grid; - error = ctx->context->encode_grid(pixel_tiles, - rows, columns, - encoder, - options, - out_grid); - if (error != Error::Ok) { - return error.error_struct(ctx->context.get()); + std::shared_ptr out_grid; + auto addGridResult = ImageItem_Grid::add_and_encode_full_grid(ctx->context.get(), + pixel_tiles, + rows, columns, + encoder, + options); + if (addGridResult.error) { + return addGridResult.error.error_struct(ctx->context.get()); } // Mark as primary image @@ -2924,6 +3452,182 @@ struct heif_error heif_context_encode_grid(struct heif_context* ctx, } +struct heif_error heif_context_add_grid_image(struct heif_context* ctx, + uint32_t image_width, + uint32_t image_height, + uint32_t tile_columns, + uint32_t tile_rows, + const struct heif_encoding_options* encoding_options, + struct heif_image_handle** out_grid_image_handle) +{ + if (tile_rows == 0 || tile_columns == 0) { + return Error(heif_error_Usage_error, + heif_suberror_Invalid_parameter_value).error_struct(ctx->context.get()); + } + else if (tile_rows > 0xFFFF || tile_columns > 0xFFFF) { + return heif_error{heif_error_Usage_error, + heif_suberror_Invalid_image_size, + "Number of tile rows/columns may not exceed 65535"}; + } + + std::shared_ptr gridimage; + auto generateGridItemResult = ImageItem_Grid::add_new_grid_item(ctx->context.get(), + image_width, + image_height, + static_cast(tile_rows), + static_cast(tile_columns), + encoding_options); + if (generateGridItemResult.error) { + return generateGridItemResult.error.error_struct(ctx->context.get()); + } + + if (out_grid_image_handle) { + *out_grid_image_handle = new heif_image_handle; + (*out_grid_image_handle)->image = gridimage; + (*out_grid_image_handle)->context = ctx->context; + } + + return heif_error_success; +} + + +struct heif_error heif_context_add_overlay_image(struct heif_context* ctx, + uint32_t image_width, + uint32_t image_height, + uint16_t nImages, + const heif_item_id* image_ids, + int32_t* offsets, + const uint16_t background_rgba[4], + struct heif_image_handle** out_iovl_image_handle) +{ + if (!image_ids) { + return Error(heif_error_Usage_error, + heif_suberror_Null_pointer_argument).error_struct(ctx->context.get()); + } + else if (nImages == 0) { + return Error(heif_error_Usage_error, + heif_suberror_Invalid_parameter_value).error_struct(ctx->context.get()); + } + + + std::vector refs; + refs.insert(refs.end(), image_ids, image_ids + nImages); + + ImageOverlay overlay; + overlay.set_canvas_size(image_width, image_height); + + if (background_rgba) { + overlay.set_background_color(background_rgba); + } + + for (uint16_t i=0;i> addImageResult = ImageItem_Overlay::add_new_overlay_item(ctx->context.get(), overlay); + + if (addImageResult.error != Error::Ok) { + return addImageResult.error.error_struct(ctx->context.get()); + } + + std::shared_ptr iovlimage = addImageResult.value; + + + if (out_iovl_image_handle) { + *out_iovl_image_handle = new heif_image_handle; + (*out_iovl_image_handle)->image = iovlimage; + (*out_iovl_image_handle)->context = ctx->context; + } + + return heif_error_success; +} + + +struct heif_error heif_context_add_tiled_image(struct heif_context* ctx, + const struct heif_tiled_image_parameters* parameters, + const struct heif_encoding_options* options, // TODO: do we need this? + const struct heif_encoder* encoder, + struct heif_image_handle** out_grid_image_handle) +{ + Result> gridImageResult; + gridImageResult = ImageItem_Tiled::add_new_tiled_item(ctx->context.get(), parameters, encoder); + + if (gridImageResult.error != Error::Ok) { + return gridImageResult.error.error_struct(ctx->context.get()); + } + + if (out_grid_image_handle) { + *out_grid_image_handle = new heif_image_handle; + (*out_grid_image_handle)->image = gridImageResult.value; + (*out_grid_image_handle)->context = ctx->context; + } + + return heif_error_success; +} + + +struct heif_error heif_context_add_image_tile(struct heif_context* ctx, + struct heif_image_handle* tiled_image, + uint32_t tile_x, uint32_t tile_y, + const struct heif_image* image, + struct heif_encoder* encoder) +{ + if (auto tili_image = std::dynamic_pointer_cast(tiled_image->image)) { + Error err = tili_image->add_image_tile(tile_x, tile_y, image->image, encoder); + return err.error_struct(ctx->context.get()); + } +#if WITH_UNCOMPRESSED_CODEC + else if (auto unci = std::dynamic_pointer_cast(tiled_image->image)) { + Error err = unci->add_image_tile(tile_x, tile_y, image->image); + return err.error_struct(ctx->context.get()); + } +#endif + else if (auto grid_item = std::dynamic_pointer_cast(tiled_image->image)) { + Error err = grid_item->add_image_tile(tiled_image->image->get_id(), tile_x, tile_y, image->image, encoder); + return err.error_struct(ctx->context.get()); + } + else { + return { + heif_error_Usage_error, + heif_suberror_Unspecified, + "Cannot add tile to a non-tiled image" + }; + } +} + + +struct heif_error heif_context_add_unci_image(struct heif_context* ctx, + const struct heif_unci_image_parameters* parameters, + const struct heif_encoding_options* encoding_options, + const heif_image* prototype, + struct heif_image_handle** out_unci_image_handle) +{ +#if WITH_UNCOMPRESSED_CODEC + Result> unciImageResult; + unciImageResult = ImageItem_uncompressed::add_unci_item(ctx->context.get(), parameters, encoding_options, prototype->image); + + if (unciImageResult.error != Error::Ok) { + return unciImageResult.error.error_struct(ctx->context.get()); + } + + if (out_unci_image_handle) { + *out_unci_image_handle = new heif_image_handle; + (*out_unci_image_handle)->image = unciImageResult.value; + (*out_unci_image_handle)->context = ctx->context; + } + + return heif_error_success; +#else + return {heif_error_Unsupported_feature, + heif_suberror_Unspecified, + "support for uncompressed images (ISO23001-17) has been disabled."}; +#endif +} + + + struct heif_error heif_context_assign_thumbnail(struct heif_context* ctx, const struct heif_image_handle* master_image, const struct heif_image_handle* thumbnail_image) @@ -2941,7 +3645,7 @@ struct heif_error heif_context_encode_thumbnail(struct heif_context* ctx, int bbox_size, struct heif_image_handle** out_image_handle) { - std::shared_ptr thumbnail_image; + std::shared_ptr thumbnail_image; heif_encoding_options options; set_default_options(options); @@ -3038,8 +3742,14 @@ struct heif_error heif_context_add_generic_metadata(struct heif_context* ctx, const void* data, int size, const char* item_type, const char* content_type) { + if (item_type == nullptr || strlen(item_type) != 4) { + return {heif_error_Usage_error, + heif_suberror_Invalid_parameter_value, + "called heif_context_add_generic_metadata() with invalid 'item_type'."}; + } + Error error = ctx->context->add_generic_metadata(image_handle->image, data, size, - item_type, content_type, nullptr, heif_metadata_compression_off, nullptr); + fourcc(item_type), content_type, nullptr, heif_metadata_compression_off, nullptr); if (error != Error::Ok) { return error.error_struct(ctx->context.get()); } @@ -3056,7 +3766,7 @@ struct heif_error heif_context_add_generic_uri_metadata(struct heif_context* ctx heif_item_id* out_item_id) { Error error = ctx->context->add_generic_metadata(image_handle->image, data, size, - "uri ", nullptr, item_uri_type, heif_metadata_compression_off, out_item_id); + fourcc("uri "), nullptr, item_uri_type, heif_metadata_compression_off, out_item_id); if (error != Error::Ok) { return error.error_struct(ctx->context.get()); } @@ -3066,9 +3776,9 @@ struct heif_error heif_context_add_generic_uri_metadata(struct heif_context* ctx } -void heif_context_set_maximum_image_size_limit(struct heif_context* ctx, int maximum_width) +void heif_context_set_maximum_image_size_limit(struct heif_context* ctx, int maximum_pixels) { - ctx->context->set_maximum_image_size_limit(maximum_width); + ctx->context->get_security_limits()->max_image_size_pixels = maximum_pixels; } diff --git a/libheif/api/libheif/heif.h b/libheif/api/libheif/heif.h index a560c3a77b..5d78668381 100644 --- a/libheif/api/libheif/heif.h +++ b/libheif/api/libheif/heif.h @@ -52,6 +52,8 @@ extern "C" { // 1.14 3 5 1 1 1 1 // 1.15 4 5 1 1 1 1 // 1.16 5 6 1 1 1 1 +// 1.18 5 7 1 1 1 1 +// 1.19 5 7 2 1 1 1 #if defined(_MSC_VER) && !defined(LIBHEIF_STATIC_BUILD) #ifdef LIBHEIF_EXPORTS @@ -241,6 +243,13 @@ enum heif_suberror_code heif_suberror_No_vvcC_box = 141, + // icbr is only needed in some situations, this error is for those cases + heif_suberror_No_icbr_box = 142, + + heif_suberror_No_avcC_box = 143, + + // Decompressing generic compression or header compression data failed (e.g. bitstream corruption) + heif_suberror_Decompression_invalid_data = 150, // --- Memory_allocation_error --- @@ -249,6 +258,9 @@ enum heif_suberror_code // security limits further. heif_suberror_Security_limit_exceeded = 1000, + // There was an error from the underlying compression / decompression library. + // One possibility is lack of resources (e.g. memory). + heif_suberror_Compression_initialisation_error = 1001, // --- Usage_error --- @@ -297,6 +309,10 @@ enum heif_suberror_code heif_suberror_Unsupported_header_compression_method = 3005, + // Generically compressed data used an unsupported compression method + heif_suberror_Unsupported_generic_compression_method = 3006, + + heif_suberror_Unsupported_essential_property = 3007, // --- Encoder_plugin_error --- @@ -413,7 +429,7 @@ enum heif_compression_format /** * Uncompressed encoding. * - * This is defined in ISO/IEC 23001-17:2023 (Final Draft International Standard). + * This is defined in ISO/IEC 23001-17:2024. */ heif_compression_uncompressed = 8, /** @@ -472,7 +488,10 @@ enum heif_colorspace heif_colorspace_RGB = 1, // heif_colorspace_monochrome should only be used with heif_chroma = heif_chroma_monochrome - heif_colorspace_monochrome = 2 + heif_colorspace_monochrome = 2, + + // Indicates that this image has no visual channels. + heif_colorspace_nonvisual = 3 }; enum heif_channel @@ -484,9 +503,21 @@ enum heif_channel heif_channel_G = 4, heif_channel_B = 5, heif_channel_Alpha = 6, - heif_channel_interleaved = 10 + heif_channel_interleaved = 10, + heif_channel_filter_array = 11, + heif_channel_depth = 12, + heif_channel_disparity = 13 }; +enum heif_metadata_compression +{ + heif_metadata_compression_off = 0, + heif_metadata_compression_auto = 1, + heif_metadata_compression_unknown = 2, // only used when reading unknown method from input file + heif_metadata_compression_deflate = 3, + heif_metadata_compression_zlib = 4, // do not use for header data + heif_metadata_compression_brotli = 5 +}; // ========================= library initialization ====================== @@ -902,15 +933,33 @@ LIBHEIF_API void heif_context_free(struct heif_context*); + struct heif_reading_options; enum heif_reader_grow_status { - heif_reader_grow_status_size_reached, // requested size has been reached, we can read until this point - heif_reader_grow_status_timeout, // size has not been reached yet, but it may still grow further - heif_reader_grow_status_size_beyond_eof // size has not been reached and never will. The file has grown to its full size + heif_reader_grow_status_size_reached, // requested size has been reached, we can read until this point + heif_reader_grow_status_timeout, // size has not been reached yet, but it may still grow further (deprecated) + heif_reader_grow_status_size_beyond_eof, // size has not been reached and never will. The file has grown to its full size + heif_reader_grow_status_error // an error has occurred +}; + +struct heif_reader_range_request_result +{ + enum heif_reader_grow_status status; // should not return 'heif_reader_grow_status_timeout' + + // Indicates up to what position the file has been read. + // If we cannot read the whole file range (status == 'heif_reader_grow_status_size_beyond_eof'), this is the actual end position. + // On the other hand, it may be that the reader was reading more data than requested. In that case, it should indicate the full size here + // and libheif may decide to make use of the additional data (e.g. for filling 'tili' offset tables). + uint64_t range_end; + + // for status == 'heif_reader_grow_status_error' + int reader_error_code; // a reader specific error code + const char* reader_error_msg; // libheif will call heif_reader.release_error_msg on this if it is not NULL }; + struct heif_reader { // API version supported by this reader @@ -919,7 +968,7 @@ struct heif_reader // --- version 1 functions --- int64_t (* get_position)(void* userdata); - // The functions read(), and seek() return heif_error_ok on success. + // The functions read(), and seek() return 0 on success. // Generally, libheif will make sure that we do not read past the file size. int (* read)(void* data, size_t size, @@ -937,6 +986,41 @@ struct heif_reader // detection whether the target_size is above the (fixed) file length // (in this case, return 'size_beyond_eof'). enum heif_reader_grow_status (* wait_for_file_size)(int64_t target_size, void* userdata); + + // --- version 2 functions --- + + // These two functions are for applications that want to stream HEIF files on demand. + // For example, a large HEIF file that is served over HTTPS and we only want to download + // it partially to decode individual tiles. + // If you do not have this use case, you do not have to implement these functions and + // you can set them to NULL. For simple linear loading, you may use the 'wait_for_file_size' + // function above instead. + + // If this function is defined, libheif will often request a file range before accessing it. + // `end_pos` is one byte after the last position to be read. + // You should return + // - 'heif_reader_grow_status_size_reached' if the requested range is available, or + // - 'heif_reader_grow_status_size_beyond_eof' if the requested range exceeds the file size + // (the valid part of the range has been read). + struct heif_reader_range_request_result (*request_range)(uint64_t start_pos, uint64_t end_pos, void* userdata); + + // libheif might issue hints when it assumes that a file range might be needed in the future. + // This may happen, for example, when your are doing selective tile accesses and libheif proposes + // to preload offset pointer tables. + // Another difference to request_file_range() is that this call should be non-blocking. + // If you preload any data, do this in a background thread. + void (*preload_range_hint)(uint64_t start_pos, uint64_t end_pos, void* userdata); + + // If libheif does not need access to a file range anymore, it may call this function to + // give a hint to the reader that it may release the range from a cache. + // If you do not maintain a file cache that wants to reduce its size dynamically, you do not + // need to implement this function. + void (*release_file_range)(uint64_t start_pos, uint64_t end_pos, void* userdata); + + // Release an error message that was returned by heif_reader in an earlier call. + // If this function is NULL, the error message string will not be released. + // This is a viable option if you are only returning static strings. + void (*release_error_msg)(const char* msg); }; @@ -1006,7 +1090,7 @@ void heif_context_debug_dump_boxes_to_file(struct heif_context* ctx, int fd); LIBHEIF_API -void heif_context_set_maximum_image_size_limit(struct heif_context* ctx, int maximum_width); +void heif_context_set_maximum_image_size_limit(struct heif_context* ctx, int maximum_pixels); // If the maximum threads number is set to 0, the image tiles are decoded in the main thread. // This is different from setting it to 1, which will generate a single background thread to decode the tiles. @@ -1017,6 +1101,46 @@ LIBHEIF_API void heif_context_set_max_decoding_threads(struct heif_context* ctx, int max_threads); +// --- security limits + +// If you set a limit to 0, the limit is disabled. +struct heif_security_limits { + uint8_t version; + + // --- version 1 + + // Limit on the maximum image size to avoid allocating too much memory. + // 32768^2 = 1.5 GB as YUV-4:2:0 or 4 GB as RGB32 + uint64_t max_image_size_pixels; + uint64_t max_number_of_tiles; + uint32_t max_bayer_pattern_pixels; + uint32_t max_items; + + uint32_t max_color_profile_size; + uint64_t max_memory_block_size; + + uint32_t max_iloc_items; + uint32_t max_iloc_extents_per_item; + + uint32_t max_children_per_box; // for all boxes that are not covered by other limits +}; + +// The global security limits are the default for new heif_contexts. +// These global limits cannot be changed, but you can override the limits for a specific heif_context. +LIBHEIF_API +const struct heif_security_limits* heif_get_global_security_limits(); + +// Returns the security limits for a heif_context. +// By default, the limits are set to the global limits, but you can change them in the returned object. +LIBHEIF_API +struct heif_security_limits* heif_context_get_security_limits(const struct heif_context*); + +// Overwrites the security limits of a heif_context. +// This is a convenience function to easily copy limits. +LIBHEIF_API +struct heif_error heif_context_set_security_limits(struct heif_context*, const struct heif_security_limits*); + + // ========================= heif_image_handle ========================= // An heif_image_handle is a handle to a logical image in the HEIF file. @@ -1052,17 +1176,21 @@ LIBHEIF_API int heif_image_handle_is_premultiplied_alpha(const struct heif_image_handle*); // Returns -1 on error, e.g. if this information is not present in the image. +// Only defined for images coded in the YCbCr or monochrome colorspace. LIBHEIF_API int heif_image_handle_get_luma_bits_per_pixel(const struct heif_image_handle*); // Returns -1 on error, e.g. if this information is not present in the image. +// Only defined for images coded in the YCbCr colorspace. LIBHEIF_API int heif_image_handle_get_chroma_bits_per_pixel(const struct heif_image_handle*); // Return the colorspace that libheif proposes to use for decoding. // Usually, these will be either YCbCr or Monochrome, but it may also propose RGB for images -// encoded with matrix_coefficients=0. +// encoded with matrix_coefficients=0 or for images coded natively in RGB. // It may also return *_undefined if the file misses relevant information to determine this without decoding. +// These are only proposed values that avoid colorspace conversions as much as possible. +// You can still request the output in your preferred colorspace, but this may involve an internal conversion. LIBHEIF_API struct heif_error heif_image_handle_get_preferred_decoding_colorspace(const struct heif_image_handle* image_handle, enum heif_colorspace* out_colorspace, @@ -1089,6 +1217,83 @@ LIBHEIF_API struct heif_context* heif_image_handle_get_context(const struct heif_image_handle* handle); +struct heif_image_tiling +{ + int version; + + // --- version 1 + + uint32_t num_columns; + uint32_t num_rows; + uint32_t tile_width; + uint32_t tile_height; + + uint32_t image_width; + uint32_t image_height; + + // Position of the top left tile. + // Usually, this is (0;0), but if a tiled image is rotated or cropped, it may be that the top left tile should be placed at a negative position. + // The offsets define this negative shift. + uint32_t top_offset; + uint32_t left_offset; + + uint8_t number_of_extra_dimensions; // 0 for normal images, 1 for volumetric (3D), ... + uint32_t extra_dimension_size[8]; // size of extra dimensions (first 8 dimensions) +}; + + +// If 'process_image_transformations' is true, this returns modified sizes. +// If it is false, the top_offset and left_offset will always be (0;0). +LIBHEIF_API +struct heif_error heif_image_handle_get_image_tiling(const struct heif_image_handle* handle, int process_image_transformations, struct heif_image_tiling* out_tiling); + + +// For grid images, return the image item ID of a specific grid tile. +// If 'process_image_transformations' is true, the tile positions are given in the transformed image coordinate system and +// are internally mapped to the original image tile positions. +LIBHEIF_API +struct heif_error heif_image_handle_get_grid_image_tile_id(const struct heif_image_handle* handle, + int process_image_transformations, + uint32_t tile_x, uint32_t tile_y, + heif_item_id* out_tile_item_id); + + +struct heif_decoding_options; + +// The tile position is given in tile indices, not in pixel coordinates. +// If the image transformations are processed (option->ignore_image_transformations==false), the tile position +// is given in the transformed coordinates. +LIBHEIF_API +struct heif_error heif_image_handle_decode_image_tile(const struct heif_image_handle* in_handle, + struct heif_image** out_img, + enum heif_colorspace colorspace, + enum heif_chroma chroma, + const struct heif_decoding_options* options, + uint32_t tile_x, uint32_t tile_y); + + +// ------------------------- entity groups ------------------------ + +typedef uint32_t heif_entity_group_id; + +struct heif_entity_group +{ + heif_entity_group_id entity_group_id; + uint32_t entity_group_type; // this is a FourCC constant + heif_item_id* entities; + uint32_t num_entities; +}; + +// Use 0 for `type_filter` or `item_filter` to disable the filter. +// Returns an array of heif_entity_group structs with *out_num_groups entries. +LIBHEIF_API +struct heif_entity_group* heif_context_get_entity_groups(const struct heif_context*, uint32_t type_filter, uint32_t item_filter, int* out_num_groups); + +// Release an array of entity groups returned by heif_context_get_entity_groups(). +LIBHEIF_API +void heif_entity_groups_release(struct heif_entity_group*, int num_groups); + + // ------------------------- depth images ------------------------- LIBHEIF_API @@ -1472,8 +1677,10 @@ enum heif_chroma_upsampling_algorithm heif_chroma_upsampling_bilinear = 2 }; + struct heif_color_conversion_options { + // 'version' must be 1. uint8_t version; // --- version 1 options @@ -1488,8 +1695,15 @@ struct heif_color_conversion_options // Set this field to 'true' if you want to make sure that the specified algorithm is used even // at the cost of slightly higher computation times. uint8_t only_use_preferred_chroma_algorithm; + + // --- Note that we cannot extend this struct because it is embedded in + // other structs (heif_decoding_options and heif_encoding_options). }; +// Assumes that it is a version=1 struct. +LIBHEIF_API +void heif_color_conversion_options_set_defaults(struct heif_color_conversion_options*); + struct heif_decoding_options { @@ -1619,7 +1833,6 @@ struct heif_error heif_image_crop(struct heif_image* img, LIBHEIF_API int heif_image_get_bits_per_pixel(const struct heif_image*, enum heif_channel channel); - // Get the number of bits per pixel in the given image channel. This function returns // the number of bits used for representing the pixel value, which might be smaller // than the number of bits used in memory. @@ -1647,6 +1860,7 @@ uint8_t* heif_image_get_plane(struct heif_image*, int* out_stride); + struct heif_scaling_options; // Currently, heif_scaling_options is not defined yet. Pass a NULL pointer. @@ -1656,6 +1870,12 @@ struct heif_error heif_image_scale_image(const struct heif_image* input, int width, int height, const struct heif_scaling_options* options); +// Extends the image size to match the given size by extending the right and bottom borders. +// The border areas are filled with zero. +LIBHEIF_API +struct heif_error heif_image_extend_to_size_fill_with_zero(struct heif_image* image, + uint32_t width, uint32_t height); + // The color profile is not attached to the image handle because we might need it // for color space transform and encoding. LIBHEIF_API @@ -1709,6 +1929,10 @@ int heif_image_has_content_light_level(const struct heif_image*); LIBHEIF_API void heif_image_get_content_light_level(const struct heif_image*, struct heif_content_light_level* out); +// Returns whether the image has 'content light level' information. If 0 is returned, the output is not filled. +LIBHEIF_API +int heif_image_handle_get_content_light_level(const struct heif_image_handle*, struct heif_content_light_level* out); + LIBHEIF_API void heif_image_set_content_light_level(const struct heif_image*, const struct heif_content_light_level* in); @@ -1741,9 +1965,14 @@ int heif_image_has_mastering_display_colour_volume(const struct heif_image*); LIBHEIF_API void heif_image_get_mastering_display_colour_volume(const struct heif_image*, struct heif_mastering_display_colour_volume* out); +// Returns whether the image has 'mastering display colour volume' information. If 0 is returned, the output is not filled. +LIBHEIF_API +int heif_image_handle_get_mastering_display_colour_volume(const struct heif_image_handle*, struct heif_mastering_display_colour_volume* out); + LIBHEIF_API void heif_image_set_mastering_display_colour_volume(const struct heif_image*, const struct heif_mastering_display_colour_volume* in); + // Converts the internal numeric representation of heif_mastering_display_colour_volume to the // normalized values, collected in heif_decoded_mastering_display_colour_volume. // Values that are out-of-range are decoded to 0, indicating an undefined value (as specified in ISO/IEC 23008-2). @@ -1754,6 +1983,10 @@ struct heif_error heif_mastering_display_colour_volume_decode(const struct heif_ LIBHEIF_API void heif_image_get_pixel_aspect_ratio(const struct heif_image*, uint32_t* aspect_h, uint32_t* aspect_v); +// Returns whether the image has 'pixel aspect ratio information' information. If 0 is returned, the output is filled with the 1:1 default. +LIBHEIF_API +int heif_image_handle_get_pixel_aspect_ratio(const struct heif_image_handle*, uint32_t* aspect_h, uint32_t* aspect_v); + LIBHEIF_API void heif_image_set_pixel_aspect_ratio(struct heif_image*, uint32_t aspect_h, uint32_t aspect_v); @@ -2078,7 +2311,7 @@ struct heif_encoding_options // Set this to the NCLX parameters to be used in the output image or set to NULL // when the same parameters as in the input image should be used. - struct heif_color_profile_nclx* output_nclx_profile; + const struct heif_color_profile_nclx* output_nclx_profile; uint8_t macOS_compatibility_workaround_no_nclx_profile; @@ -2093,7 +2326,7 @@ struct heif_encoding_options // version 7 options - // Set this to true to use compressed form of uncC where possible + // Set this to true to use compressed form of uncC where possible. uint8_t prefer_uncC_short_form; }; @@ -2137,6 +2370,34 @@ struct heif_error heif_context_encode_grid(struct heif_context* ctx, const struct heif_encoding_options* input_options, struct heif_image_handle** out_image_handle); +LIBHEIF_API +struct heif_error heif_context_add_grid_image(struct heif_context* ctx, + uint32_t image_width, + uint32_t image_height, + uint32_t tile_columns, + uint32_t tile_rows, + const struct heif_encoding_options* encoding_options, + struct heif_image_handle** out_grid_image_handle); + +LIBHEIF_API +struct heif_error heif_context_add_image_tile(struct heif_context* ctx, + struct heif_image_handle* tiled_image, + uint32_t tile_x, uint32_t tile_y, + const struct heif_image* image, + struct heif_encoder* encoder); + +// offsets[] should either be NULL (all offsets==0) or an array of size 2*nImages with x;y offset pairs. +// If background_rgba is NULL, the background is transparent. +LIBHEIF_API +struct heif_error heif_context_add_overlay_image(struct heif_context* ctx, + uint32_t image_width, + uint32_t image_height, + uint16_t nImages, + const heif_item_id* image_ids, + int32_t* offsets, + const uint16_t background_rgba[4], + struct heif_image_handle** out_iovl_image_handle); + LIBHEIF_API struct heif_error heif_context_set_primary_image(struct heif_context*, struct heif_image_handle* image_handle); @@ -2157,14 +2418,6 @@ struct heif_error heif_context_encode_thumbnail(struct heif_context*, int bbox_size, struct heif_image_handle** out_thumb_image_handle); -enum heif_metadata_compression -{ - heif_metadata_compression_off, - heif_metadata_compression_auto, - heif_metadata_compression_deflate, - heif_metadata_compression_unknown -}; - // Assign 'thumbnail_image' as the thumbnail image of 'master_image'. LIBHEIF_API struct heif_error heif_context_assign_thumbnail(struct heif_context*, diff --git a/libheif/api/libheif/heif_emscripten.h b/libheif/api/libheif/heif_emscripten.h index cb219f24bc..518f92e300 100644 --- a/libheif/api/libheif/heif_emscripten.h +++ b/libheif/api/libheif/heif_emscripten.h @@ -24,6 +24,11 @@ static struct heif_error _heif_context_read_from_memory( return heif_context_read_from_memory(context, data.data(), data.size(), nullptr); } +static heif_filetype_result heif_js_check_filetype(const std::string& data) +{ + return heif_check_filetype((const uint8_t*) data.data(), data.size()); +} + static emscripten::val heif_js_context_get_image_handle( struct heif_context* context, heif_item_id id) { @@ -41,6 +46,25 @@ static emscripten::val heif_js_context_get_image_handle( return emscripten::val(handle); } +static emscripten::val heif_js_context_get_primary_image_handle( + struct heif_context* context) +{ + emscripten::val result = emscripten::val::object(); + if (!context) { + return result; + } + + heif_image_handle* handle; + struct heif_error err = heif_context_get_primary_image_handle(context, &handle); + + if (err.code != heif_error_Ok) { + return emscripten::val(err); + } + + return emscripten::val(handle); +} + + static emscripten::val heif_js_context_get_list_of_top_level_image_IDs( struct heif_context* context) { @@ -272,11 +296,15 @@ EMSCRIPTEN_BINDINGS(libheif) { EXPORT_HEIF_FUNCTION(heif_context_free); emscripten::function("heif_context_read_from_memory", &_heif_context_read_from_memory, emscripten::allow_raw_pointers()); + emscripten::function("heif_js_check_filetype", + &heif_js_check_filetype, emscripten::allow_raw_pointers()); EXPORT_HEIF_FUNCTION(heif_context_get_number_of_top_level_images); emscripten::function("heif_js_context_get_list_of_top_level_image_IDs", &heif_js_context_get_list_of_top_level_image_IDs, emscripten::allow_raw_pointers()); emscripten::function("heif_js_context_get_image_handle", &heif_js_context_get_image_handle, emscripten::allow_raw_pointers()); + emscripten::function("heif_js_context_get_primary_image_handle", + &heif_js_context_get_primary_image_handle, emscripten::allow_raw_pointers()); //emscripten::function("heif_js_decode_image", //&heif_js_decode_image, emscripten::allow_raw_pointers()); emscripten::function("heif_js_decode_image2", @@ -303,6 +331,8 @@ EMSCRIPTEN_BINDINGS(libheif) { emscripten::enum_("heif_suberror_code") .value("heif_suberror_Unspecified", heif_suberror_Unspecified) .value("heif_suberror_Cannot_write_output_data", heif_suberror_Cannot_write_output_data) + .value("heif_suberror_Compression_initialisation_error", heif_suberror_Compression_initialisation_error) + .value("heif_suberror_Decompression_invalid_data", heif_suberror_Decompression_invalid_data) .value("heif_suberror_Encoder_initialization", heif_suberror_Encoder_initialization) .value("heif_suberror_Encoder_encoding", heif_suberror_Encoder_encoding) .value("heif_suberror_Encoder_cleanup", heif_suberror_Encoder_cleanup) @@ -329,6 +359,7 @@ EMSCRIPTEN_BINDINGS(libheif) { .value("heif_suberror_Invalid_grid_data", heif_suberror_Invalid_grid_data) .value("heif_suberror_Missing_grid_images", heif_suberror_Missing_grid_images) .value("heif_suberror_No_av1C_box", heif_suberror_No_av1C_box) + .value("heif_suberror_No_avcC_box", heif_suberror_No_avcC_box) .value("heif_suberror_Invalid_clean_aperture", heif_suberror_Invalid_clean_aperture) .value("heif_suberror_Invalid_overlay_data", heif_suberror_Invalid_overlay_data) .value("heif_suberror_Overlay_image_outside_of_canvas", heif_suberror_Overlay_image_outside_of_canvas) @@ -358,6 +389,8 @@ EMSCRIPTEN_BINDINGS(libheif) { .value("heif_suberror_Unsupported_codec", heif_suberror_Unsupported_codec) .value("heif_suberror_Unsupported_image_type", heif_suberror_Unsupported_image_type) .value("heif_suberror_Unsupported_data_version", heif_suberror_Unsupported_data_version) + .value("heif_suberror_Unsupported_generic_compression_method", heif_suberror_Unsupported_generic_compression_method) + .value("heif_suberror_Unsupported_essential_property", heif_suberror_Unsupported_essential_property) .value("heif_suberror_Unsupported_color_conversion", heif_suberror_Unsupported_color_conversion) .value("heif_suberror_Unsupported_item_construction_method", heif_suberror_Unsupported_item_construction_method) .value("heif_suberror_Unsupported_header_compression_method", heif_suberror_Unsupported_header_compression_method) @@ -369,7 +402,8 @@ EMSCRIPTEN_BINDINGS(libheif) { .value("heif_suberror_No_ispe_property", heif_suberror_No_ispe_property) .value("heif_suberror_Camera_intrinsic_matrix_undefined", heif_suberror_Camera_intrinsic_matrix_undefined) .value("heif_suberror_Camera_extrinsic_matrix_undefined", heif_suberror_Camera_extrinsic_matrix_undefined) - .value("heif_suberror_Invalid_J2K_codestream", heif_suberror_Invalid_J2K_codestream); + .value("heif_suberror_Invalid_J2K_codestream", heif_suberror_Invalid_J2K_codestream) + .value("heif_suberror_No_icbr_box", heif_suberror_No_icbr_box); emscripten::enum_("heif_compression_format") .value("heif_compression_undefined", heif_compression_undefined) @@ -409,7 +443,8 @@ EMSCRIPTEN_BINDINGS(libheif) { .value("heif_colorspace_undefined", heif_colorspace_undefined) .value("heif_colorspace_YCbCr", heif_colorspace_YCbCr) .value("heif_colorspace_RGB", heif_colorspace_RGB) - .value("heif_colorspace_monochrome", heif_colorspace_monochrome); + .value("heif_colorspace_monochrome", heif_colorspace_monochrome) + .value("heif_colorspace_nonvisual", heif_colorspace_nonvisual); emscripten::enum_("heif_channel") .value("heif_channel_Y", heif_channel_Y) .value("heif_channel_Cr", heif_channel_Cr) @@ -418,7 +453,16 @@ EMSCRIPTEN_BINDINGS(libheif) { .value("heif_channel_G", heif_channel_G) .value("heif_channel_B", heif_channel_B) .value("heif_channel_Alpha", heif_channel_Alpha) - .value("heif_channel_interleaved", heif_channel_interleaved); + .value("heif_channel_interleaved", heif_channel_interleaved) + .value("heif_channel_filter_array", heif_channel_filter_array) + .value("heif_channel_depth", heif_channel_depth) + .value("heif_channel_disparity", heif_channel_disparity); + + emscripten::enum_("heif_filetype_result") + .value("heif_filetype_no", heif_filetype_no) + .value("heif_filetype_yes_supported", heif_filetype_yes_supported) + .value("heif_filetype_yes_unsupported", heif_filetype_yes_unsupported) + .value("heif_filetype_maybe", heif_filetype_maybe); emscripten::class_("heif_context"); emscripten::class_("heif_image_handle"); diff --git a/libheif/api/libheif/heif_experimental.h b/libheif/api/libheif/heif_experimental.h index c86dd908db..2999942b7b 100644 --- a/libheif/api/libheif/heif_experimental.h +++ b/libheif/api/libheif/heif_experimental.h @@ -27,6 +27,7 @@ extern "C" { #endif +#if WITH_EXPERIMENTAL_FEATURES /* =================================================================================== * This file contains candidate APIs that did not make it into the public API yet. @@ -109,6 +110,235 @@ struct heif_error heif_property_camera_extrinsic_matrix_get_position_vector(cons //LIBHEIF_API struct heif_error heif_property_camera_extrinsic_matrix_get_world_coordinate_system_id(const struct heif_property_camera_extrinsic_matrix* matrix, uint32_t* out_wcs_id); +#endif + +// --- Tiled images + +struct heif_tiled_image_parameters { + int version; + + // --- version 1 + + uint32_t image_width; + uint32_t image_height; + + uint32_t tile_width; + uint32_t tile_height; + + uint32_t compression_format_fourcc; // will be set automatically when calling heif_context_add_tiled_image() + + uint8_t offset_field_length; // one of: 32, 40, 48, 64 + uint8_t size_field_length; // one of: 0, 24, 32, 64 + + uint8_t number_of_extra_dimensions; // 0 for normal images, 1 for volumetric (3D), ... + uint32_t extra_dimensions[8]; // size of extra dimensions (first 8 dimensions) + + // boolean flags + uint8_t tiles_are_sequential; // TODO: can we derive this automatically +}; + +#if WITH_EXPERIMENTAL_FEATURES +LIBHEIF_API +struct heif_error heif_context_add_tiled_image(struct heif_context* ctx, + const struct heif_tiled_image_parameters* parameters, + const struct heif_encoding_options* options, // TODO: do we need this? + const struct heif_encoder* encoder, + struct heif_image_handle** out_tiled_image_handle); +#endif + +// --- 'unci' images + +struct heif_unci_image_parameters { + int version; + + // --- version 1 + + uint32_t image_width; + uint32_t image_height; + + uint32_t tile_width; + uint32_t tile_height; + + enum heif_metadata_compression compression; // TODO + + // TODO: interleave type, padding +}; + +#if WITH_EXPERIMENTAL_FEATURES +LIBHEIF_API +struct heif_error heif_context_add_unci_image(struct heif_context* ctx, + const struct heif_unci_image_parameters* parameters, + const struct heif_encoding_options* encoding_options, + const struct heif_image* prototype, + struct heif_image_handle** out_unci_image_handle); +#endif + +// --- 'pymd' entity group (pyramid layers) + +struct heif_pyramid_layer_info { + heif_item_id layer_image_id; + uint16_t layer_binning; + uint32_t tiles_in_layer_row; + uint32_t tiles_in_layer_column; +}; + +#if WITH_EXPERIMENTAL_FEATURES +// The input images are automatically sorted according to resolution. You can provide them in any order. +LIBHEIF_API +struct heif_error heif_context_add_pyramid_entity_group(struct heif_context* ctx, + const heif_item_id* layer_item_ids, + size_t num_layers, + heif_item_id* out_group_id); + +LIBHEIF_API +struct heif_pyramid_layer_info* heif_context_get_pyramid_entity_group_info(struct heif_context*, heif_entity_group_id id, int* out_num_layers); + +LIBHEIF_API +void heif_pyramid_layer_info_release(struct heif_pyramid_layer_info*); +#endif + +// --- other pixel datatype support + +enum heif_channel_datatype +{ + heif_channel_datatype_undefined = 0, + heif_channel_datatype_unsigned_integer = 1, + heif_channel_datatype_signed_integer = 2, + heif_channel_datatype_floating_point = 3, + heif_channel_datatype_complex_number = 4 +}; + +#if WITH_EXPERIMENTAL_FEATURES +LIBHEIF_API +struct heif_error heif_image_add_channel(struct heif_image* image, + enum heif_channel channel, + int width, int height, + enum heif_channel_datatype datatype, int bit_depth); + + +LIBHEIF_API +int heif_image_list_channels(struct heif_image*, + enum heif_channel** out_channels); + +LIBHEIF_API +void heif_channel_release_list(enum heif_channel** channels); +#endif + +struct heif_complex32 { + float real, imaginary; +}; + +struct heif_complex64 { + double real, imaginary; +}; + +#if WITH_EXPERIMENTAL_FEATURES +LIBHEIF_API +enum heif_channel_datatype heif_image_get_datatype(const struct heif_image* img, enum heif_channel channel); + + +// The 'stride' in all of these functions are in units of the underlying datatype. +LIBHEIF_API +const uint16_t* heif_image_get_channel_uint16_readonly(const struct heif_image*, + enum heif_channel channel, + uint32_t* out_stride); + +LIBHEIF_API +const uint32_t* heif_image_get_channel_uint32_readonly(const struct heif_image*, + enum heif_channel channel, + uint32_t* out_stride); + +LIBHEIF_API +const uint64_t* heif_image_get_channel_uint64_readonly(const struct heif_image*, + enum heif_channel channel, + uint32_t* out_stride); + +LIBHEIF_API +const int16_t* heif_image_get_channel_int16_readonly(const struct heif_image*, + enum heif_channel channel, + uint32_t* out_stride); + +LIBHEIF_API +const int32_t* heif_image_get_channel_int32_readonly(const struct heif_image*, + enum heif_channel channel, + uint32_t* out_stride); + +LIBHEIF_API +const int64_t* heif_image_get_channel_int64_readonly(const struct heif_image*, + enum heif_channel channel, + uint32_t* out_stride); + +LIBHEIF_API +const float* heif_image_get_channel_float32_readonly(const struct heif_image*, + enum heif_channel channel, + uint32_t* out_stride); + +LIBHEIF_API +const double* heif_image_get_channel_float64_readonly(const struct heif_image*, + enum heif_channel channel, + uint32_t* out_stride); + +LIBHEIF_API +const struct heif_complex32* heif_image_get_channel_complex32_readonly(const struct heif_image*, + enum heif_channel channel, + uint32_t* out_stride); + +LIBHEIF_API +const struct heif_complex64* heif_image_get_channel_complex64_readonly(const struct heif_image*, + enum heif_channel channel, + uint32_t* out_stride); + +LIBHEIF_API +uint16_t* heif_image_get_channel_uint16(struct heif_image*, + enum heif_channel channel, + uint32_t* out_stride); + +LIBHEIF_API +uint32_t* heif_image_get_channel_uint32(struct heif_image*, + enum heif_channel channel, + uint32_t* out_stride); + +LIBHEIF_API +uint64_t* heif_image_get_channel_uint64(struct heif_image*, + enum heif_channel channel, + uint32_t* out_stride); + +LIBHEIF_API +int16_t* heif_image_get_channel_int16(struct heif_image*, + enum heif_channel channel, + uint32_t* out_stride); + +LIBHEIF_API +int32_t* heif_image_get_channel_int32(struct heif_image*, + enum heif_channel channel, + uint32_t* out_stride); + +LIBHEIF_API +int64_t* heif_image_get_channel_int64(struct heif_image*, + enum heif_channel channel, + uint32_t* out_stride); + +LIBHEIF_API +float* heif_image_get_channel_float32(struct heif_image*, + enum heif_channel channel, + uint32_t* out_stride); + +LIBHEIF_API +double* heif_image_get_channel_float64(struct heif_image*, + enum heif_channel channel, + uint32_t* out_stride); + +LIBHEIF_API +struct heif_complex32* heif_image_get_channel_complex32(struct heif_image*, + enum heif_channel channel, + uint32_t* out_stride); + +LIBHEIF_API +struct heif_complex64* heif_image_get_channel_complex64(struct heif_image*, + enum heif_channel channel, + uint32_t* out_stride); + +#endif #ifdef __cplusplus } diff --git a/libheif/api/libheif/heif_items.cc b/libheif/api/libheif/heif_items.cc index dd96dd4ae4..044bec5938 100644 --- a/libheif/api/libheif/heif_items.cc +++ b/libheif/api/libheif/heif_items.cc @@ -61,13 +61,7 @@ int heif_context_get_list_of_item_IDs(const struct heif_context* ctx, uint32_t heif_context_get_item_type(const struct heif_context* ctx, heif_item_id item_id) { - auto type = ctx->context->get_heif_file()->get_item_type(item_id); - if (type.empty()) { - return 0; - } - else { - return fourcc(type.c_str()); - } + return ctx->context->get_heif_file()->get_item_type_4cc(item_id); } @@ -88,7 +82,7 @@ const char* heif_context_get_mime_item_content_type(const struct heif_context* c auto infe = ctx->context->get_heif_file()->get_infe_box(item_id); if (!infe) { return nullptr; } - if (infe->get_item_type() != "mime") { + if (infe->get_item_type_4cc() != fourcc("mime")) { return nullptr; } @@ -100,7 +94,7 @@ const char* heif_context_get_mime_item_content_encoding(const struct heif_contex auto infe = ctx->context->get_heif_file()->get_infe_box(item_id); if (!infe) { return nullptr; } - if (infe->get_item_type() != "mime") { + if (infe->get_item_type_4cc() != fourcc("mime")) { return nullptr; } @@ -113,7 +107,7 @@ const char* heif_context_get_uri_item_uri_type(const struct heif_context* ctx, h auto infe = ctx->context->get_heif_file()->get_infe_box(item_id); if (!infe) { return nullptr; } - if (infe->get_item_type() != "uri ") { + if (infe->get_item_type_4cc() != fourcc("uri ")) { return nullptr; } @@ -178,7 +172,7 @@ struct heif_error heif_context_get_item_data(struct heif_context* ctx, void* out_data) { std::vector data; - Error err = ctx->context->get_heif_file()->get_compressed_image_data(item_id, &data); + Error err = ctx->context->get_heif_file()->get_uncompressed_item_data(item_id, &data); if (err) { return err.error_struct(ctx->context.get()); @@ -244,7 +238,13 @@ struct heif_error heif_context_add_item(struct heif_context* ctx, const void* data, int size, heif_item_id* out_item_id) { - Result result = ctx->context->get_heif_file()->add_infe(item_type, (const uint8_t*) data, size); + if (item_type == nullptr || strlen(item_type) != 4) { + return {heif_error_Usage_error, + heif_suberror_Invalid_parameter_value, + "called heif_context_add_item() with invalid 'item_type'."}; + } + + Result result = ctx->context->get_heif_file()->add_infe(fourcc(item_type), (const uint8_t*) data, size); if (result && out_item_id) { *out_item_id = result.value; diff --git a/libheif/api/libheif/heif_properties.cc b/libheif/api/libheif/heif_properties.cc index af97cbd1cf..08c9d7f9d8 100644 --- a/libheif/api/libheif/heif_properties.cc +++ b/libheif/api/libheif/heif_properties.cc @@ -254,7 +254,7 @@ int heif_item_get_property_transform_rotation_ccw(const struct heif_context* con return -1; } - return irot->get_rotation(); + return irot->get_rotation_ccw(); } @@ -499,15 +499,12 @@ struct heif_error heif_property_get_tai_timestamp(const struct heif_context* ctx } -struct heif_error heif_item_get_property_raw_size(const struct heif_context* context, - heif_item_id itemId, - heif_property_id propertyId, - size_t* size_out) +template +struct heif_error find_property(const struct heif_context* context, + heif_item_id itemId, + heif_property_id propertyId, + std::shared_ptr* box_casted) { - if (!context || !size_out) { - return {heif_error_Usage_error, heif_suberror_Null_pointer_argument, "NULL argument passed in"}; - } - auto file = context->context->get_heif_file(); std::vector> properties; @@ -516,12 +513,29 @@ struct heif_error heif_item_get_property_raw_size(const struct heif_context* con return err.error_struct(context->context.get()); } - if (propertyId - 1 < 0 || propertyId - 1 >= properties.size()) { + if (propertyId < 1 || propertyId - 1 >= properties.size()) { return {heif_error_Usage_error, heif_suberror_Invalid_property, "property index out of range"}; } auto box = properties[propertyId - 1]; - auto box_other = std::dynamic_pointer_cast(box); + *box_casted = std::dynamic_pointer_cast(box); + return heif_error_success; +} + + +struct heif_error heif_item_get_property_raw_size(const struct heif_context* context, + heif_item_id itemId, + heif_property_id propertyId, + size_t* size_out) +{ + if (!context || !size_out) { + return {heif_error_Usage_error, heif_suberror_Null_pointer_argument, "NULL argument passed in"}; + } + std::shared_ptr box_other; + struct heif_error err = find_property(context, itemId, propertyId, &box_other); + if (err.code) { + return err; + } // TODO: every Box (not just Box_other) should have a get_raw_data() method. if (box_other == nullptr) { @@ -536,31 +550,21 @@ struct heif_error heif_item_get_property_raw_size(const struct heif_context* con } -struct heif_error heif_item_get_property_uuid(const struct heif_context* context, - heif_item_id itemId, - heif_property_id propertyId, - uint8_t* data_out) +struct heif_error heif_item_get_property_raw_data(const struct heif_context* context, + heif_item_id itemId, + heif_property_id propertyId, + uint8_t* data_out) { if (!context || !data_out) { return {heif_error_Usage_error, heif_suberror_Null_pointer_argument, "NULL argument passed in"}; } - auto file = context->context->get_heif_file(); - - std::vector> properties; - Error err = file->get_properties(itemId, properties); - if (err) { - return err.error_struct(context->context.get()); - } - - if (propertyId - 1 < 0 || propertyId - 1 >= properties.size()) { - return {heif_error_Usage_error, heif_suberror_Invalid_property, "property index out of range"}; + std::shared_ptr box_other; + struct heif_error err = find_property(context, itemId, propertyId, &box_other); + if (err.code) { + return err; } - - auto box = properties[propertyId - 1]; - auto box_other = std::dynamic_pointer_cast(box); - // TODO: every Box (not just Box_other) should have a get_raw_data() method. if (box_other == nullptr) { return {heif_error_Usage_error, heif_suberror_Invalid_property, "this property is not read as a raw box"}; @@ -573,3 +577,29 @@ struct heif_error heif_item_get_property_uuid(const struct heif_context* context return heif_error_success; } + +struct heif_error heif_item_get_property_uuid_type(const struct heif_context* context, + heif_item_id itemId, + heif_property_id propertyId, + uint8_t extended_type[16]) +{ + if (!context || !extended_type) { + return {heif_error_Usage_error, heif_suberror_Null_pointer_argument, "NULL argument passed in"}; + } + + std::shared_ptr box_other; + struct heif_error err = find_property(context, itemId, propertyId, &box_other); + if (err.code) { + return err; + } + + if (box_other == nullptr) { + return {heif_error_Usage_error, heif_suberror_Invalid_property, "this property is not read as a raw box"}; + } + + auto uuid = box_other->get_uuid_type(); + + std::copy(uuid.begin(), uuid.end(), extended_type); + + return heif_error_success; +} diff --git a/libheif/api/libheif/heif_properties.h b/libheif/api/libheif/heif_properties.h index 0f03428b11..fdc48a5f47 100644 --- a/libheif/api/libheif/heif_properties.h +++ b/libheif/api/libheif/heif_properties.h @@ -168,6 +168,25 @@ struct heif_error heif_item_get_property_raw_data(const struct heif_context* con heif_property_id propertyId, uint8_t* out_data); +/** + * Get the extended type for an extended "uuid" box. + * + * This provides the UUID for the extended box. + * + * This method should only be called on properties of type `heif_item_property_type_uuid`. + * + * @param context the heif_context containing the HEIF file + * @param itemId the image item id to which this property belongs. + * @param propertyID the property index (1-based) to get the extended type for + * @param extended_type output of the call, must be a pointer to at least 16-bytes. + * @return heif_error_success or an error indicating the failure + */ +LIBHEIF_API +struct heif_error heif_item_get_property_uuid_type(const struct heif_context* context, + heif_item_id itemId, + heif_property_id propertyId, + uint8_t extended_type[16]); + // ========================= Timestamps ========================= LIBHEIF_API extern const uint64_t heif_tai_clock_info_unknown_time_uncertainty; diff --git a/libheif/bitstream.cc b/libheif/bitstream.cc index 9cdf1e585d..9d35d78de0 100644 --- a/libheif/bitstream.cc +++ b/libheif/bitstream.cc @@ -26,6 +26,8 @@ #define MAX_UVLC_LEADING_ZEROS 20 +#define AVOID_FUZZER_FALSE_POSITIVE 0 + StreamReader_istream::StreamReader_istream(std::unique_ptr&& istr) : m_istr(std::move(istr)) @@ -35,19 +37,19 @@ StreamReader_istream::StreamReader_istream(std::unique_ptr&& istr) m_istr->seekg(0, std::ios_base::beg); } -int64_t StreamReader_istream::get_position() const +uint64_t StreamReader_istream::get_position() const { return m_istr->tellg(); } -StreamReader::grow_status StreamReader_istream::wait_for_file_size(int64_t target_size) +StreamReader::grow_status StreamReader_istream::wait_for_file_size(uint64_t target_size) { - return (target_size > m_length) ? size_beyond_eof : size_reached; + return (target_size > m_length) ? grow_status::size_beyond_eof : grow_status::size_reached; } bool StreamReader_istream::read(void* data, size_t size) { - int64_t end_pos = get_position() + size; + uint64_t end_pos = get_position() + size; if (end_pos > m_length) { return false; } @@ -56,7 +58,7 @@ bool StreamReader_istream::read(void* data, size_t size) return true; } -bool StreamReader_istream::seek(int64_t position) +bool StreamReader_istream::seek(uint64_t position) { if (position > m_length) return false; @@ -72,7 +74,7 @@ StreamReader_memory::StreamReader_memory(const uint8_t* data, size_t size, bool { if (copy) { m_owned_data = new uint8_t[m_length]; - memcpy(m_owned_data, data, m_length); + memcpy(m_owned_data, data, size); m_data = m_owned_data; } @@ -88,19 +90,19 @@ StreamReader_memory::~StreamReader_memory() } } -int64_t StreamReader_memory::get_position() const +uint64_t StreamReader_memory::get_position() const { return m_position; } -StreamReader::grow_status StreamReader_memory::wait_for_file_size(int64_t target_size) +StreamReader::grow_status StreamReader_memory::wait_for_file_size(uint64_t target_size) { - return (target_size > m_length) ? size_beyond_eof : size_reached; + return (target_size > m_length) ? grow_status::size_beyond_eof : grow_status::size_reached; } bool StreamReader_memory::read(void* data, size_t size) { - int64_t end_pos = m_position + size; + uint64_t end_pos = m_position + size; if (end_pos > m_length) { return false; } @@ -111,7 +113,7 @@ bool StreamReader_memory::read(void* data, size_t size) return true; } -bool StreamReader_memory::seek(int64_t position) +bool StreamReader_memory::seek(uint64_t position) { if (position > m_length || position < 0) return false; @@ -126,19 +128,19 @@ StreamReader_CApi::StreamReader_CApi(const heif_reader* func_table, void* userda { } -StreamReader::grow_status StreamReader_CApi::wait_for_file_size(int64_t target_size) +StreamReader::grow_status StreamReader_CApi::wait_for_file_size(uint64_t target_size) { heif_reader_grow_status status = m_func_table->wait_for_file_size(target_size, m_userdata); switch (status) { case heif_reader_grow_status_size_reached: - return size_reached; + return grow_status::size_reached; case heif_reader_grow_status_timeout: - return timeout; + return grow_status::timeout; case heif_reader_grow_status_size_beyond_eof: - return size_beyond_eof; + return grow_status::size_beyond_eof; default: assert(0); - return size_beyond_eof; + return grow_status::size_beyond_eof; } } @@ -154,6 +156,17 @@ BitstreamRange::BitstreamRange(std::shared_ptr istr, } +BitstreamRange::BitstreamRange(std::shared_ptr istr, + size_t start, + size_t end) // one past end + : m_istr(std::move(istr)), m_remaining(end) +{ + bool success = m_istr->seek(start); + assert(success); + (void)success; // TODO +} + + StreamReader::grow_status BitstreamRange::wait_until_range_is_available() { return m_istr->wait_for_file_size(m_istr->get_position() + m_remaining); @@ -205,7 +218,8 @@ int16_t BitstreamRange::read16s() uint16_t v = read16(); if (v & 0x8000) { - return -static_cast((~v) & 0x7fff) -1; + auto val = static_cast((~v) & 0x7fff); + return static_cast(-val - 1); } else { return static_cast(v); @@ -213,6 +227,27 @@ int16_t BitstreamRange::read16s() } +uint32_t BitstreamRange::read24() +{ + if (!prepare_read(3)) { + return 0; + } + + uint8_t buf[3]; + + auto istr = get_istream(); + bool success = istr->read((char*) buf, 3); + + if (!success) { + set_eof_while_reading(); + return 0; + } + + return (uint32_t) ((buf[0] << 16) | + (buf[1] << 8) | + (buf[2])); +} + uint32_t BitstreamRange::read32() { if (!prepare_read(4)) { @@ -235,6 +270,33 @@ uint32_t BitstreamRange::read32() (buf[3])); } +float BitstreamRange::readFloat32() +{ + int i = read32(); + float f; + memcpy(&f, &i, sizeof(float)); + return f; +} + + +uint64_t BitstreamRange::read_uint(int len) +{ + switch (len) + { + case 8: + return read8(); + case 16: + return read16(); + case 32: + return read32(); + case 64: + return read64(); + default: + assert(false); + return 0; + } +} + int32_t BitstreamRange::read32s() { @@ -289,33 +351,6 @@ int64_t BitstreamRange::read64s() } -float BitstreamRange::read_float32() -{ - assert(sizeof(float)==4); - - if (!prepare_read(4)) { - return 0; - } - - uint8_t buf[4]; - - auto istr = get_istream(); - bool success = istr->read((char*) buf, 4); - - if (!success) { - set_eof_while_reading(); - return 0; - } - -#if !IS_BIG_ENDIAN - std::swap(buf[0],buf[3]); - std::swap(buf[1],buf[2]); -#endif - - return *reinterpret_cast(buf); -} - - std::string BitstreamRange::read_string() { std::string str; @@ -429,8 +464,11 @@ BitReader::BitReader(const uint8_t* buffer, int len) refill(); } -int BitReader::get_bits(int n) + +uint32_t BitReader::get_bits(int n) { + assert(n <= 32); + if (nextbits_cnt < n) { refill(); } @@ -438,12 +476,29 @@ int BitReader::get_bits(int n) uint64_t val = nextbits; val >>= 64 - n; + if (AVOID_FUZZER_FALSE_POSITIVE) nextbits &= (0xffffffffffffffffULL >> n); + nextbits <<= n; nextbits_cnt -= n; - return (int) val; + return static_cast(val); +} + + +uint8_t BitReader::get_bits8(int n) +{ + assert(n>0 && n <= 8); + return static_cast(get_bits(n)); +} + + +uint32_t BitReader::get_bits32(int n) +{ + assert(n>0 && n <= 32); + return static_cast(get_bits(n)); } + int BitReader::get_bits_fast(int n) { assert(nextbits_cnt >= n); @@ -483,12 +538,14 @@ void BitReader::skip_bits(int n) refill(); } + if (AVOID_FUZZER_FALSE_POSITIVE) nextbits &= (0xffffffffffffffffULL >> n); nextbits <<= n; nextbits_cnt -= n; } void BitReader::skip_bits_fast(int n) { + if (AVOID_FUZZER_FALSE_POSITIVE) nextbits &= (0xffffffffffffffffULL >> n); nextbits <<= n; nextbits_cnt -= n; } @@ -497,6 +554,7 @@ void BitReader::skip_to_byte_boundary() { int nskip = (nextbits_cnt & 7); + if (AVOID_FUZZER_FALSE_POSITIVE) nextbits &= (0xffffffffffffffffULL >> nskip); nextbits <<= nskip; nextbits_cnt -= nskip; } @@ -601,13 +659,28 @@ void StreamWriter::write16s(int16_t v16s) v = static_cast(v16s); } else { - v = ~static_cast((-v16s-1)); + auto val = static_cast((-v16s-1)); + v = static_cast(~val); } write16(v); } +void StreamWriter::write24(uint32_t v) +{ + size_t required_size = m_position + 3; + + if (required_size > m_data.size()) { + m_data.resize(required_size); + } + + m_data[m_position++] = uint8_t((v >> 16) & 0xFF); + m_data[m_position++] = uint8_t((v >> 8) & 0xFF); + m_data[m_position++] = uint8_t(v & 0xFF); +} + + void StreamWriter::write32(uint32_t v) { size_t required_size = m_position + 4; @@ -623,6 +696,14 @@ void StreamWriter::write32(uint32_t v) } +void StreamWriter::writeFloat32(float v) +{ + uint32_t i; + memcpy(&i, &v, sizeof(float)); + write32(i); +} + + void StreamWriter::write32s(int32_t v32s) { uint32_t v; @@ -656,36 +737,6 @@ void StreamWriter::write64(uint64_t v) } -void StreamWriter::write64(int64_t v) -{ - write64(reinterpret_cast(v)); -} - - -void StreamWriter::write_float32(float v) -{ - assert(sizeof(float)==4); - - uint8_t buf[4]; - *reinterpret_cast(buf) = v; - -#if !IS_BIG_ENDIAN - std::swap(buf[0], buf[3]); - std::swap(buf[1], buf[2]); -#endif - - if (m_position + 4 > m_data.size()) { - m_data.resize(m_position + 4); - } - - m_data[m_position++] = buf[0]; - m_data[m_position++] = buf[1]; - m_data[m_position++] = buf[2]; - m_data[m_position++] = buf[3]; -} - - - void StreamWriter::write(int size, uint64_t value) { if (size == 1) { diff --git a/libheif/bitstream.h b/libheif/bitstream.h index 13a03dbce8..4de8c2ed38 100644 --- a/libheif/bitstream.h +++ b/libheif/bitstream.h @@ -30,8 +30,10 @@ #include #include #include +#include #include "error.h" +#include class StreamReader @@ -39,9 +41,9 @@ class StreamReader public: virtual ~StreamReader() = default; - virtual int64_t get_position() const = 0; + virtual uint64_t get_position() const = 0; - enum grow_status + enum class grow_status : uint8_t { size_reached, // requested size has been reached timeout, // size has not been reached yet, but it may still grow further @@ -49,36 +51,72 @@ class StreamReader }; // a StreamReader can maintain a timeout for waiting for new data - virtual grow_status wait_for_file_size(int64_t target_size) = 0; + virtual grow_status wait_for_file_size(uint64_t target_size) = 0; // returns 'false' when we read out of the available file size virtual bool read(void* data, size_t size) = 0; - virtual bool seek(int64_t position) = 0; + virtual bool seek(uint64_t position) = 0; - bool seek_cur(int64_t position_offset) + bool seek_cur(uint64_t position_offset) { return seek(get_position() + position_offset); } + + // Informs the reader implementation that we will process data in the given range. + // The reader can use this information to retrieve a larger chunk of data instead of individual read() calls. + // Returns the file size that was made available, but you still have to check each read() call. + // Returning a value shorter than the requested range end indicates to libheif that the data is not available. + // Returns 0 on error. + virtual uint64_t request_range(uint64_t start, uint64_t end_pos) { + return std::numeric_limits::max(); + } + + virtual void release_range(uint64_t start, uint64_t end_pos) { } + + virtual void preload_range_hint(uint64_t start, uint64_t end_pos) { } + + Error get_error() const { + return m_last_error; + } + + void clear_last_error() { m_last_error = {}; } + +protected: + Error m_last_error; }; +#include class StreamReader_istream : public StreamReader { public: StreamReader_istream(std::unique_ptr&& istr); - int64_t get_position() const override; + uint64_t get_position() const override; - grow_status wait_for_file_size(int64_t target_size) override; + grow_status wait_for_file_size(uint64_t target_size) override; bool read(void* data, size_t size) override; - bool seek(int64_t position) override; + bool seek(uint64_t position) override; + + uint64_t request_range(uint64_t start, uint64_t end_pos) override { + // std::cout << "[istream] request_range " << start << " - " << end_pos << "\n"; + return end_pos; + } + + void release_range(uint64_t start, uint64_t end_pos) override { + // std::cout << "[istream] release_range " << start << " - " << end_pos << "\n"; + } + + void preload_range_hint(uint64_t start, uint64_t end_pos) override { + // std::cout << "[istream] preload_range_hint " << start << " - " << end_pos << "\n"; + } private: std::unique_ptr m_istr; - int64_t m_length; + uint64_t m_length; }; @@ -89,18 +127,23 @@ class StreamReader_memory : public StreamReader ~StreamReader_memory() override; - int64_t get_position() const override; + uint64_t get_position() const override; - grow_status wait_for_file_size(int64_t target_size) override; + grow_status wait_for_file_size(uint64_t target_size) override; bool read(void* data, size_t size) override; - bool seek(int64_t position) override; + bool seek(uint64_t position) override; + + // end_pos is last byte to read + 1. I.e. like a file size. + uint64_t request_range(uint64_t start, uint64_t end_pos) override { + return m_length; + } private: const uint8_t* m_data; - int64_t m_length; - int64_t m_position; + uint64_t m_length; + uint64_t m_position; // if we made a copy of the data, we store a pointer to the owned memory area here uint8_t* m_owned_data = nullptr; @@ -112,13 +155,72 @@ class StreamReader_CApi : public StreamReader public: StreamReader_CApi(const heif_reader* func_table, void* userdata); - int64_t get_position() const override { return m_func_table->get_position(m_userdata); } + uint64_t get_position() const override { return m_func_table->get_position(m_userdata); } - StreamReader::grow_status wait_for_file_size(int64_t target_size) override; + StreamReader::grow_status wait_for_file_size(uint64_t target_size) override; bool read(void* data, size_t size) override { return !m_func_table->read(data, size, m_userdata); } - bool seek(int64_t position) override { return !m_func_table->seek(position, m_userdata); } + bool seek(uint64_t position) override { return !m_func_table->seek(position, m_userdata); } + + uint64_t request_range(uint64_t start, uint64_t end_pos) override { + if (m_func_table->reader_api_version >= 2) { + heif_reader_range_request_result result = m_func_table->request_range(start, end_pos, m_userdata); + + // convert error message string and release input string memory + + std::string error_msg; + if (result.reader_error_msg) { + error_msg = std::string{result.reader_error_msg}; + + if (m_func_table->release_error_msg) { + m_func_table->release_error_msg(result.reader_error_msg); + } + } + + switch (result.status) { + case heif_reader_grow_status_size_reached: + return end_pos; + case heif_reader_grow_status_timeout: + return 0; // invalid return value from callback + case heif_reader_grow_status_size_beyond_eof: + m_last_error = {heif_error_Invalid_input, heif_suberror_End_of_data, "Read beyond file size"}; + return result.range_end; + case heif_reader_grow_status_error: { + if (result.reader_error_msg) { + std::stringstream sstr; + sstr << "Input error (" << result.reader_error_code << ") : " << error_msg; + m_last_error = {heif_error_Invalid_input, heif_suberror_Unspecified, sstr.str()}; + } + else { + std::stringstream sstr; + sstr << "Input error (" << result.reader_error_code << ")"; + m_last_error = {heif_error_Invalid_input, heif_suberror_Unspecified, sstr.str()}; + } + + return 0; // error occurred + } + default: + m_last_error = {heif_error_Invalid_input, heif_suberror_Unspecified, "Invalid input reader return value"}; + return 0; + } + } + else { + return std::numeric_limits::max(); + } + } + + void release_range(uint64_t start, uint64_t end_pos) override { + if (m_func_table->reader_api_version >= 2) { + m_func_table->release_file_range(start, end_pos, m_userdata); + } + } + + void preload_range_hint(uint64_t start, uint64_t end_pos) override { + if (m_func_table->reader_api_version >= 2) { + m_func_table->preload_range_hint(start, end_pos, m_userdata); + } + } private: const heif_reader* m_func_table; @@ -135,6 +237,10 @@ class BitstreamRange size_t length, BitstreamRange* parent = nullptr); + BitstreamRange(std::shared_ptr istr, + size_t start, + size_t end); // one past end + // This function tries to make sure that the full data of this range is // available. You should call this before starting reading the range. // If you don't, you have to make sure that you do not read past the available data. @@ -146,15 +252,29 @@ class BitstreamRange int16_t read16s(); + /** + * Read 24 bit unsigned integer from the bitstream. + * + * The data is assumed to be in big endian format and is returned as a 32 bit value. + */ + uint32_t read24(); + uint32_t read32(); int32_t read32s(); uint64_t read64(); - int64_t read64s(); + uint64_t read_uint(int len); + + /** + * Read 32 bit floating point value from the bitstream. + * + * The data is assumed to be in big endian format. + */ + float readFloat32(); - float read_float32(); + int64_t read64s(); std::string read_string(); @@ -175,6 +295,21 @@ class BitstreamRange } } + void skip(uint64_t n) + { + size_t actual_skip = std::min(static_cast(n), m_remaining); + + if (m_parent_range) { + // also advance position in parent range + m_parent_range->skip_without_advancing_file_pos(actual_skip); + } + + assert(actual_skip <= static_cast(std::numeric_limits::max())); + + m_istr->seek_cur(static_cast(actual_skip)); + m_remaining -= actual_skip; + } + void skip_to_end_of_box() { if (m_remaining > 0) { @@ -244,7 +379,11 @@ class BitReader public: BitReader(const uint8_t* buffer, int len); - int get_bits(int n); + uint32_t get_bits(int n); + + uint8_t get_bits8(int n); + + uint32_t get_bits32(int n); int get_bits_fast(int n); @@ -293,13 +432,15 @@ class StreamWriter void write16s(int16_t); + void write24(uint32_t); + void write32(uint32_t); void write32s(int32_t); void write64(uint64_t); - void write64(int64_t); + void writeFloat32(float); void write(int size, uint64_t value); @@ -309,8 +450,6 @@ class StreamWriter void write(const StreamWriter&); - void write_float32(float); - void skip(int n); void insert(int nBytes); diff --git a/libheif/box.cc b/libheif/box.cc index b30f742a72..3dbedbc1a7 100644 --- a/libheif/box.cc +++ b/libheif/box.cc @@ -23,12 +23,14 @@ #include "box.h" #include "security_limits.h" #include "nclx.h" -#include "codecs/jpeg.h" -#include "codecs/jpeg2000.h" -#include "codecs/hevc.h" -#include "codecs/mask_image.h" -#include "codecs/vvc.h" -#include "codecs/avc.h" +#include "codecs/jpeg_boxes.h" +#include "codecs/jpeg2000_boxes.h" +#include "codecs/hevc_boxes.h" +#include "image-items/mask_image.h" +#include "codecs/vvc_boxes.h" +#include "codecs/avc_boxes.h" +#include "codecs/avif_boxes.h" +#include "image-items/tiled.h" #include #include @@ -38,15 +40,24 @@ #include #include #include +#include + #if WITH_UNCOMPRESSED_CODEC -#include "codecs/uncompressed_box.h" +#include "codecs/uncompressed/unc_boxes.h" #endif #ifndef M_PI #define M_PI 3.14159265358979323846 #endif +#if !defined(_WIN32) +#include +#else +#include +#include +#endif + Fraction::Fraction(int32_t num, int32_t den) { @@ -151,25 +162,6 @@ bool Fraction::is_valid() const return denominator != 0; } -uint32_t from_fourcc(const char* string) -{ - return ((string[0] << 24) | - (string[1] << 16) | - (string[2] << 8) | - (string[3])); -} - -std::string to_fourcc(uint32_t code) -{ - std::string str(" "); - str[0] = static_cast((code >> 24) & 0xFF); - str[1] = static_cast((code >> 16) & 0xFF); - str[2] = static_cast((code >> 8) & 0xFF); - str[3] = static_cast((code >> 0) & 0xFF); - - return str; -} - BoxHeader::BoxHeader() = default; @@ -211,7 +203,7 @@ std::string BoxHeader::get_type_string() const return sstr.str(); } else { - return to_fourcc(m_type); + return fourcc_to_string(m_type); } } @@ -237,7 +229,7 @@ Error BoxHeader::parse_header(BitstreamRange& range) { StreamReader::grow_status status; status = range.wait_for_available_bytes(8); - if (status != StreamReader::size_reached) { + if (status != StreamReader::grow_status::size_reached) { // TODO: return recoverable error at timeout return Error(heif_error_Invalid_input, heif_suberror_End_of_data); @@ -250,7 +242,7 @@ Error BoxHeader::parse_header(BitstreamRange& range) if (m_size == 1) { status = range.wait_for_available_bytes(8); - if (status != StreamReader::size_reached) { + if (status != StreamReader::grow_status::size_reached) { // TODO: return recoverable error at timeout return Error(heif_error_Invalid_input, heif_suberror_End_of_data); @@ -274,7 +266,7 @@ Error BoxHeader::parse_header(BitstreamRange& range) if (m_type == fourcc("uuid")) { status = range.wait_for_available_bytes(16); - if (status != StreamReader::size_reached) { + if (status != StreamReader::grow_status::size_reached) { // TODO: return recoverable error at timeout return Error(heif_error_Invalid_input, heif_suberror_End_of_data); @@ -405,7 +397,7 @@ std::string BoxHeader::dump(Indent& indent) const } -Error Box::parse(BitstreamRange& range) +Error Box::parse(BitstreamRange& range, const heif_security_limits* limits) { // skip box @@ -414,12 +406,15 @@ Error Box::parse(BitstreamRange& range) } else { uint64_t content_size = get_box_size() - get_header_size(); - if (range.prepare_read(content_size)) { - if (content_size > MAX_BOX_SIZE) { - return Error(heif_error_Invalid_input, - heif_suberror_Invalid_box_size); - } + assert(MAX_BOX_SIZE <= SIZE_MAX); + + if (content_size > MAX_BOX_SIZE) { + return Error(heif_error_Invalid_input, + heif_suberror_Invalid_box_size); + } + + if (range.prepare_read(static_cast(content_size))) { range.get_istream()->seek_cur(get_box_size() - get_header_size()); } } @@ -444,7 +439,7 @@ Error FullBox::parse_full_box_header(BitstreamRange& range) } -Error Box::read(BitstreamRange& range, std::shared_ptr* result) +Error Box::read(BitstreamRange& range, std::shared_ptr* result, const heif_security_limits* limits) { BoxHeader hdr; Error err = hdr.parse_header(range); @@ -456,6 +451,8 @@ Error Box::read(BitstreamRange& range, std::shared_ptr* result) return range.get_error(); } + result->reset(); + std::shared_ptr box; switch (hdr.get_short_type()) { @@ -543,6 +540,18 @@ Error Box::read(BitstreamRange& range, std::shared_ptr* result) box = std::make_shared(); break; + case fourcc("pymd"): + box = std::make_shared(); + break; + + case fourcc("altr"): + box = std::make_shared(); + break; + + case fourcc("ster"): + box = std::make_shared(); + break; + case fourcc("dinf"): box = std::make_shared(); break; @@ -611,6 +620,18 @@ Error Box::read(BitstreamRange& range, std::shared_ptr* result) case fourcc("uncC"): box = std::make_shared(); break; + + case fourcc("cmpC"): + box = std::make_shared(); + break; + + case fourcc("icef"): + box = std::make_shared(); + break; + + case fourcc("cpat"): + box = std::make_shared(); + break; #endif // --- JPEG 2000 @@ -657,6 +678,12 @@ Error Box::read(BitstreamRange& range, std::shared_ptr* result) box = std::make_shared(); break; +#if WITH_EXPERIMENTAL_FEATURES + case fourcc("tilC"): + box = std::make_shared(); + break; +#endif + case fourcc("mdat"): // avoid generating a 'Box_other' box = std::make_shared(); @@ -681,16 +708,7 @@ Error Box::read(BitstreamRange& range, std::shared_ptr* result) box->set_short_header(hdr); - if (hdr.has_fixed_box_size() && hdr.get_box_size() < hdr.get_header_size()) { - std::stringstream sstr; - sstr << "Box size (" << hdr.get_box_size() << " bytes) smaller than header size (" - << hdr.get_header_size() << " bytes)"; - - // Sanity check. - return Error(heif_error_Invalid_input, - heif_suberror_Invalid_box_size, - sstr.str()); - } + box->m_debug_box_type = hdr.get_type_string(); // only for debugging if (range.get_nesting_level() > MAX_BOX_NESTING_LEVEL) { @@ -699,31 +717,51 @@ Error Box::read(BitstreamRange& range, std::shared_ptr* result) "Security limit for maximum nesting of boxes has been exceeded"); } - if (hdr.has_fixed_box_size()) { - auto status = range.wait_for_available_bytes(hdr.get_box_size() - hdr.get_header_size()); - if (status != StreamReader::size_reached) { - // TODO: return recoverable error at timeout - return Error(heif_error_Invalid_input, - heif_suberror_End_of_data); + // Sanity checks + if (hdr.get_box_size() < hdr.get_header_size()) { + std::stringstream sstr; + sstr << "Box size (" << hdr.get_box_size() << " bytes) smaller than header size (" + << hdr.get_header_size() << " bytes)"; + + return {heif_error_Invalid_input, + heif_suberror_Invalid_box_size, + sstr.str()}; } - } - // Security check: make sure that box size does not exceed int64 size. + // this is >= 0 because of above condition + auto nBytes = static_cast(hdr.get_box_size() - hdr.get_header_size()); + if (nBytes > SIZE_MAX) { + return {heif_error_Memory_allocation_error, + heif_suberror_Invalid_box_size, + "Box size too large"}; + } - if (hdr.get_box_size() > (uint64_t) std::numeric_limits::max()) { - return Error(heif_error_Invalid_input, - heif_suberror_Invalid_box_size); + // Security check: make sure that box size does not exceed int64 size. + + if (hdr.get_box_size() > (uint64_t) std::numeric_limits::max()) { + return {heif_error_Invalid_input, + heif_suberror_Invalid_box_size}; + } + + // --- wait for data to arrive + + auto status = range.wait_for_available_bytes(static_cast(nBytes)); + if (status != StreamReader::grow_status::size_reached) { + // TODO: return recoverable error at timeout + return {heif_error_Invalid_input, + heif_suberror_End_of_data}; + } } - int64_t box_size = static_cast(hdr.get_box_size()); + auto box_size = static_cast(hdr.get_box_size()); int64_t box_size_without_header = hdr.has_fixed_box_size() ? (box_size - hdr.get_header_size()) : (int64_t)range.get_remaining_bytes(); // Box size may not be larger than remaining bytes in parent box. if ((int64_t)range.get_remaining_bytes() < box_size_without_header) { - return Error(heif_error_Invalid_input, - heif_suberror_Invalid_box_size); + return {heif_error_Invalid_input, + heif_suberror_Invalid_box_size}; } @@ -733,12 +771,20 @@ Error Box::read(BitstreamRange& range, std::shared_ptr* result) box_size_without_header, &range); - err = box->parse(boxrange); + err = box->parse(boxrange, limits); + boxrange.skip_to_end_of_box(); + if (err == Error::Ok) { *result = std::move(box); } + else { + parse_error_fatality fatality = box->get_parse_error_fatality(); - boxrange.skip_to_end_of_box(); + box = std::make_shared(box->get_short_type(), err, fatality); + + // We return a Box_Error that represents the parse error. + *result = std::move(box); + } return err; } @@ -779,31 +825,6 @@ Error Box::write(StreamWriter& writer) const } -std::shared_ptr Box::get_child_box(uint32_t short_type) const -{ - for (auto& box : m_children) { - if (box->get_short_type() == short_type) { - return box; - } - } - - return nullptr; -} - - -std::vector> Box::get_child_boxes(uint32_t short_type) const -{ - std::vector> result; - for (auto& box : m_children) { - if (box->get_short_type() == short_type) { - result.push_back(box); - } - } - - return result; -} - - bool Box::operator==(const Box& other) const { if (this->get_short_type() != other.get_short_type()) { @@ -825,24 +846,39 @@ bool Box::equal(const std::shared_ptr& box1, const std::shared_ptr& bo if (!box1 || !box2) { return false; } + + // This was introduced because of j2kH having child boxes. + // TODO: we might also deduplicate them by comparing all child boxes. + if (box1->has_child_boxes() || box2->has_child_boxes()) { + return false; + } + return *box1 == *box2; } -Error Box::read_children(BitstreamRange& range, int max_number) +Error Box::read_children(BitstreamRange& range, uint32_t max_number, const heif_security_limits* limits) { - int count = 0; + uint32_t count = 0; while (!range.eof() && !range.error()) { std::shared_ptr box; - Error error = Box::read(range, &box); - if (error != Error::Ok) { + Error error = Box::read(range, &box, limits); + if (error != Error::Ok && (!box || box->get_parse_error_fatality() == parse_error_fatality::fatal)) { return error; } - if (m_children.size() > MAX_CHILDREN_PER_BOX) { + uint32_t max_children; + if (get_short_type() == fourcc("iinf")) { + max_children = limits->max_items; + } + else { + max_children = limits->max_children_per_box; + } + + if (max_children && m_children.size() > max_children) { std::stringstream sstr; - sstr << "Maximum number of child boxes " << MAX_CHILDREN_PER_BOX << " exceeded."; + sstr << "Maximum number of child boxes (" << max_children << ") in '" << get_type_string() << "' box exceeded."; // Sanity check. return Error(heif_error_Memory_allocation_error, @@ -880,11 +916,12 @@ Error Box::write_children(StreamWriter& writer) const } -std::string Box::dump_children(Indent& indent) const +std::string Box::dump_children(Indent& indent, bool with_index) const { std::ostringstream sstr; bool first = true; + int idx=1; indent++; for (const auto& childBox : m_children) { @@ -895,6 +932,11 @@ std::string Box::dump_children(Indent& indent) const sstr << indent << "\n"; } + if (with_index) { + sstr << indent << "index: " << idx << "\n"; + idx++; + } + sstr << childBox->dump(indent); } indent--; @@ -913,18 +955,26 @@ void Box::derive_box_version_recursive() } -Error Box_other::parse(BitstreamRange& range) +Error Box_other::parse(BitstreamRange& range, const heif_security_limits* limits) { if (has_fixed_box_size()) { size_t len; if (get_box_size() >= get_header_size()) { - len = get_box_size() - get_header_size(); + auto len64 = get_box_size() - get_header_size(); + if (len64 > MAX_BOX_SIZE) { + return {heif_error_Invalid_input, + heif_suberror_Security_limit_exceeded, + "Box size too large"}; + } + + len = static_cast(len64); + m_data.resize(len); range.read(m_data.data(), len); } else { - return Error(heif_error_Invalid_input, - heif_suberror_Invalid_box_size); + return {heif_error_Invalid_input, + heif_suberror_Invalid_box_size}; } } else { @@ -961,7 +1011,8 @@ std::string Box_other::dump(Indent& indent) const size_t len = 0; if (get_box_size() >= get_header_size()) { - len = get_box_size() - get_header_size(); + // We can cast because if it does not fit, it would fail during parsing. + len = static_cast(get_box_size() - get_header_size()); } else { sstr << indent << "invalid box size " << get_box_size() << " (smaller than header)\n"; @@ -976,12 +1027,33 @@ std::string Box_other::dump(Indent& indent) const } -Error Box_ftyp::parse(BitstreamRange& range) +std::string Box_Error::dump(Indent& indent) const +{ + std::ostringstream sstr; + sstr << indent << '\'' << fourcc_to_string(m_box_type_with_parse_error) << "' parse error: " << m_error.message << "\n"; + sstr << indent << "fatality: "; + switch (m_fatality) { + case parse_error_fatality::fatal: sstr << "fatal\n"; + case parse_error_fatality::ignorable: sstr << "ignorable\n"; + case parse_error_fatality::optional: sstr << "optional\n"; + } + + return sstr.str(); +} + +parse_error_fatality Box_Error::get_parse_error_fatality() const +{ + return m_fatality; +} + + +Error Box_ftyp::parse(BitstreamRange& range, const heif_security_limits* limits) { m_major_brand = range.read32(); m_minor_version = range.read32(); - if (get_box_size() <= get_header_size() + 8) { + uint64_t box_size = get_box_size(); + if (box_size < 8 || box_size - 8 <= get_header_size()) { // Sanity check. return Error(heif_error_Invalid_input, heif_suberror_Invalid_box_size, @@ -1013,7 +1085,7 @@ std::string Box_ftyp::dump(Indent& indent) const sstr << BoxHeader::dump(indent); - sstr << indent << "major brand: " << to_fourcc(m_major_brand) << "\n" + sstr << indent << "major brand: " << fourcc_to_string(m_major_brand) << "\n" << indent << "minor version: " << m_minor_version << "\n" << indent << "compatible brands: "; @@ -1022,7 +1094,7 @@ std::string Box_ftyp::dump(Indent& indent) const if (first) { first = false; } else { sstr << ','; } - sstr << to_fourcc(brand); + sstr << fourcc_to_string(brand); } sstr << "\n"; @@ -1055,10 +1127,14 @@ Error Box_ftyp::write(StreamWriter& writer) const } -Error Box_meta::parse(BitstreamRange& range) +Error Box_meta::parse(BitstreamRange& range, const heif_security_limits* limits) { parse_full_box_header(range); + if (get_version() != 0) { + return unsupported_version_error("meta"); + } + /* uint64_t boxSizeLimit; if (get_box_size() == BoxHeader::size_until_end_of_file) { @@ -1069,7 +1145,7 @@ Error Box_meta::parse(BitstreamRange& range) } */ - return read_children(range); + return read_children(range, READ_CHILDREN_ALL, limits); } @@ -1083,10 +1159,25 @@ std::string Box_meta::dump(Indent& indent) const } -Error Box_hdlr::parse(BitstreamRange& range) +Error FullBox::unsupported_version_error(const char* box) const +{ + std::stringstream sstr; + sstr << box << " box data version " << ((int) m_version) << " is not implemented yet"; + + return {heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + sstr.str()}; +} + + +Error Box_hdlr::parse(BitstreamRange& range, const heif_security_limits* limits) { parse_full_box_header(range); + if (get_version() != 0) { + return unsupported_version_error("hdlr"); + } + m_pre_defined = range.read32(); m_handler_type = range.read32(); @@ -1105,7 +1196,7 @@ std::string Box_hdlr::dump(Indent& indent) const std::ostringstream sstr; sstr << Box::dump(indent); sstr << indent << "pre_defined: " << m_pre_defined << "\n" - << indent << "handler_type: " << to_fourcc(m_handler_type) << "\n" + << indent << "handler_type: " << fourcc_to_string(m_handler_type) << "\n" << indent << "name: " << m_name << "\n"; return sstr.str(); @@ -1131,10 +1222,15 @@ Error Box_hdlr::write(StreamWriter& writer) const } -Error Box_pitm::parse(BitstreamRange& range) +Error Box_pitm::parse(BitstreamRange& range, const heif_security_limits* limits) { parse_full_box_header(range); + if (get_version() > 1) { + return unsupported_version_error("pitm"); + } + + if (get_version() == 0) { m_item_ID = range.read16(); } @@ -1185,10 +1281,14 @@ Error Box_pitm::write(StreamWriter& writer) const } -Error Box_iloc::parse(BitstreamRange& range) +Error Box_iloc::parse(BitstreamRange& range, const heif_security_limits* limits) { parse_full_box_header(range); + if (get_version() > 2) { + return unsupported_version_error("iloc"); + } + const int version = get_version(); uint16_t values4 = range.read16(); @@ -1210,11 +1310,12 @@ Error Box_iloc::parse(BitstreamRange& range) item_count = range.read32(); } - // Sanity check. - if (item_count > MAX_ILOC_ITEMS) { + // Sanity check. (This might be obsolete now as we check for range.error() below). + auto max_iloc_items = limits->max_iloc_items; + if (max_iloc_items && item_count > max_iloc_items) { std::stringstream sstr; sstr << "iloc box contains " << item_count << " items, which exceeds the security limit of " - << MAX_ILOC_ITEMS << " items."; + << max_iloc_items << " items."; return Error(heif_error_Memory_allocation_error, heif_suberror_Security_limit_exceeded, @@ -1224,6 +1325,15 @@ Error Box_iloc::parse(BitstreamRange& range) for (uint32_t i = 0; i < item_count; i++) { Item item; + if (range.eof()) { + std::stringstream sstr; + sstr << "iloc box should contain " << item_count << " items, but we can only read " << i << " items."; + + return {heif_error_Invalid_input, + heif_suberror_End_of_data, + sstr.str()}; + } + if (version < 2) { item.item_ID = range.read16(); } @@ -1247,13 +1357,14 @@ Error Box_iloc::parse(BitstreamRange& range) item.base_offset |= range.read32(); } - int extent_count = range.read16(); + uint16_t extent_count = range.read16(); // Sanity check. - if (extent_count > MAX_ILOC_EXTENTS_PER_ITEM) { + auto max_iloc_extents = limits->max_iloc_extents_per_item; + if (max_iloc_extents && extent_count > max_iloc_extents) { std::stringstream sstr; sstr << "Number of extents in iloc box (" << extent_count << ") exceeds security limit (" - << MAX_ILOC_EXTENTS_PER_ITEM << ")\n"; + << max_iloc_extents << ")\n"; return Error(heif_error_Memory_allocation_error, heif_suberror_Security_limit_exceeded, @@ -1263,6 +1374,15 @@ Error Box_iloc::parse(BitstreamRange& range) for (int e = 0; e < extent_count; e++) { Extent extent; + if (range.eof()) { + std::stringstream sstr; + sstr << "iloc item should contain " << extent_count << " extents, but we can only read " << e << " extents."; + + return {heif_error_Invalid_input, + heif_suberror_End_of_data, + sstr.str()}; + } + if ((version == 1 || version == 2) && index_size > 0) { if (index_size == 4) { extent.index = range.read32(); @@ -1304,6 +1424,43 @@ Error Box_iloc::parse(BitstreamRange& range) } +Box_iloc::Box_iloc() +{ + set_short_type(fourcc("iloc")); + + set_use_tmp_file(false); +} + + +Box_iloc::~Box_iloc() +{ + if (m_use_tmpfile) { + unlink(m_tmp_filename); + } +} + + +void Box_iloc::set_use_tmp_file(bool flag) +{ + m_use_tmpfile = flag; + if (flag) { +#if !defined(_WIN32) + strcpy(m_tmp_filename, "/tmp/libheif-XXXXXX"); + m_tmpfile_fd = mkstemp(m_tmp_filename); +#else + // TODO Currently unused code. Implement when needed. + assert(false); +# if 0 + char tmpname[L_tmpnam_s]; + // TODO: check return value (errno_t) + tmpnam_s(tmpname, L_tmpnam_s); + _sopen_s(&m_tmpfile_fd, tmpname, _O_CREAT | _O_TEMPORARY | _O_TRUNC | _O_RDWR, _SH_DENYRW, _S_IREAD | _S_IWRITE); +# endif +#endif + } +} + + std::string Box_iloc::dump(Indent& indent) const { std::ostringstream sstr; @@ -1331,45 +1488,68 @@ std::string Box_iloc::dump(Indent& indent) const } -Error Box_iloc::read_data(const Item& item, +Error Box_iloc::read_data(heif_item_id item, const std::shared_ptr& istr, const std::shared_ptr& idat, - std::vector* dest) const + std::vector* dest, + const heif_security_limits* limits) const { - // TODO: this function should always append the data to the output vector as this is used when - // the image data is concatenated with data in a configuration box. However, it seems that - // this function clears the array in some cases. This should be corrected. + return read_data(item, istr, idat, dest, 0, std::numeric_limits::max(), limits); +} - for (const auto& extent : item.extents) { - if (item.construction_method == 0) { - // --- security check that we do not allocate too much memory +Error Box_iloc::read_data(heif_item_id item_id, + const std::shared_ptr& istr, + const std::shared_ptr& idat, + std::vector* dest, + uint64_t offset, uint64_t size, + const heif_security_limits* limits) const +{ + const Item* item = nullptr; + for (auto& i : m_items) { + if (i.item_ID == item_id) { + item = &i; + break; + } + } - size_t old_size = dest->size(); - if (MAX_MEMORY_BLOCK_SIZE - old_size < extent.length) { - std::stringstream sstr; - sstr << "iloc box contained " << extent.length << " bytes, total memory size would be " - << (old_size + extent.length) << " bytes, exceeding the security limit of " - << MAX_MEMORY_BLOCK_SIZE << " bytes"; + if (!item) { + std::stringstream sstr; + sstr << "Item with ID " << item_id << " has no compressed data"; - return Error(heif_error_Memory_allocation_error, - heif_suberror_Security_limit_exceeded, - sstr.str()); - } + return Error(heif_error_Invalid_input, + heif_suberror_No_item_data, + sstr.str()); + } + +#if ENABLE_MULTITHREADING_SUPPORT + static std::mutex read_mutex; + + std::lock_guard lock(read_mutex); +#endif + + bool limited_size = (size != std::numeric_limits::max()); + // TODO: this function should always append the data to the output vector as this is used when + // the image data is concatenated with data in a configuration box. However, it seems that + // this function clears the array in some cases. This should be corrected. + + for (const auto& extent : item->extents) { + if (item->construction_method == 0) { + // --- make sure that all data is available if (extent.offset > MAX_FILE_POS || - item.base_offset > MAX_FILE_POS || + item->base_offset > MAX_FILE_POS || extent.length > MAX_FILE_POS) { - return Error(heif_error_Invalid_input, - heif_suberror_Security_limit_exceeded, - "iloc data pointers out of allowed range"); + return {heif_error_Invalid_input, + heif_suberror_Security_limit_exceeded, + "iloc data pointers out of allowed range"}; } - StreamReader::grow_status status = istr->wait_for_file_size(extent.offset + item.base_offset + extent.length); - if (status == StreamReader::size_beyond_eof) { + StreamReader::grow_status status = istr->wait_for_file_size(extent.offset + item->base_offset + extent.length); + if (status == StreamReader::grow_status::size_beyond_eof) { // Out-of-bounds // TODO: I think we should not clear this. Maybe we want to try reading again later and // hence should not lose the data already read. @@ -1377,53 +1557,112 @@ Error Box_iloc::read_data(const Item& item, std::stringstream sstr; sstr << "Extent in iloc box references data outside of file bounds " - << "(points to file position " << extent.offset + item.base_offset << ")\n"; + << "(points to file position " << extent.offset + item->base_offset << ")\n"; - return Error(heif_error_Invalid_input, - heif_suberror_End_of_data, - sstr.str()); + return {heif_error_Invalid_input, + heif_suberror_End_of_data, + sstr.str()}; } - else if (status == StreamReader::timeout) { + else if (status == StreamReader::grow_status::timeout) { // TODO: maybe we should introduce some 'Recoverable error' instead of 'Invalid input' - return Error(heif_error_Invalid_input, - heif_suberror_End_of_data); + return {heif_error_Invalid_input, + heif_suberror_End_of_data}; + } + + + // skip to reading offset + + uint64_t skip_len = std::min(offset, extent.length); + offset -= skip_len; + + uint64_t read_len = std::min(extent.length - skip_len, size); + + if (offset > 0) { + continue; + } + + if (read_len == 0) { + continue; + } + + size_t old_size = dest->size(); + + // --- security check that we do not allocate too much memory + + auto max_memory_block_size = limits->max_memory_block_size; + if (max_memory_block_size && max_memory_block_size - old_size < read_len) { + std::stringstream sstr; + sstr << "iloc box contained " << extent.length << " bytes, total memory size would be " + << (old_size + extent.length) << " bytes, exceeding the security limit of " + << max_memory_block_size << " bytes"; + + return {heif_error_Memory_allocation_error, + heif_suberror_Security_limit_exceeded, + sstr.str()}; + } + + + // --- request file range + + uint64_t data_start_pos = extent.offset + item->base_offset + skip_len; + uint64_t rangeRequestEndPos = istr->request_range(data_start_pos, data_start_pos + read_len); + if (rangeRequestEndPos == 0) { + return istr->get_error(); } // --- move file pointer to start of data - bool success = istr->seek(extent.offset + item.base_offset); - assert(success); - (void) success; + bool success = istr->seek(data_start_pos); + if (!success) { + return {heif_error_Invalid_input, + heif_suberror_Unspecified, + "Error setting input file position"}; + } // --- read data - dest->resize(static_cast(old_size + extent.length)); - success = istr->read((char*) dest->data() + old_size, static_cast(extent.length)); - assert(success); - (void) success; + dest->resize(static_cast(old_size + read_len)); + success = istr->read((char*) dest->data() + old_size, static_cast(read_len)); + if (!success) { + return {heif_error_Invalid_input, + heif_suberror_Unspecified, + "Error reading input file"}; + } + + size -= read_len; } - else if (item.construction_method == 1) { + else if (item->construction_method == 1) { if (!idat) { - return Error(heif_error_Invalid_input, - heif_suberror_No_idat_box, - "idat box referenced in iref box is not present in file"); + return {heif_error_Invalid_input, + heif_suberror_No_idat_box, + "idat box referenced in iref box is not present in file"}; } idat->read_data(istr, - extent.offset + item.base_offset, + extent.offset + item->base_offset, extent.length, - *dest); + *dest, limits); + + size -= extent.length; } else { std::stringstream sstr; - sstr << "Item construction method " << (int) item.construction_method << " not implemented"; - return Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_item_construction_method, - sstr.str()); + sstr << "Item construction method " << (int) item->construction_method << " not implemented"; + return {heif_error_Unsupported_feature, + heif_suberror_Unsupported_item_construction_method, + sstr.str()}; } } + // --- we could not read all data + + if (limited_size && size > 0) { + return {heif_error_Invalid_input, + heif_suberror_End_of_data, + "Not enough data present in 'iloc' to satisfy request."}; + } + return Error::Ok; } @@ -1456,7 +1695,43 @@ Error Box_iloc::append_data(heif_item_id item_ID, } Extent extent; - extent.data = data; + extent.length = data.size(); + + if (m_use_tmpfile && construction_method==0) { +#if !defined(_WIN32) + ssize_t cnt = ::write(m_tmpfile_fd, data.data(), data.size()); +#else + // TODO Currently unused code. Implement when needed. + assert(false); +# if 0 + int cnt = _write(m_tmpfile_fd, data.data(), data.size()); +# else + int cnt = -1; +# endif +#endif + if (cnt < 0) { + std::stringstream sstr; + sstr << "Could not write to tmp file: error " << errno; + return {heif_error_Encoding_error, + heif_suberror_Unspecified, + sstr.str()}; + } + else if ((size_t)cnt != data.size()) { + return {heif_error_Encoding_error, + heif_suberror_Unspecified, + "Could not write to tmp file (storage full?)"}; + } + } + else { + if (!m_items[idx].extents.empty()) { + Extent& e = m_items[idx].extents.back(); + e.data.insert(e.data.end(), data.begin(), data.end()); + e.length = e.data.size(); + return Error::Ok; + } + + extent.data = data; + } if (construction_method == 1) { extent.offset = m_idat_offset; @@ -1471,6 +1746,49 @@ Error Box_iloc::append_data(heif_item_id item_ID, } +Error Box_iloc::replace_data(heif_item_id item_ID, + uint64_t output_offset, + const std::vector& data, + uint8_t construction_method) +{ + assert(construction_method == 0); // TODO + + // check whether this item ID already exists + + size_t idx; + for (idx = 0; idx < m_items.size(); idx++) { + if (m_items[idx].item_ID == item_ID) { + break; + } + } + + assert(idx != m_items.size()); + + uint64_t data_start = 0; + for (auto& extent : m_items[idx].extents) { + if (output_offset >= extent.data.size()) { + output_offset -= extent.data.size(); + } + else { + uint64_t write_n = std::min(extent.data.size() - output_offset, + data.size() - data_start); + assert(write_n > 0); + + memcpy(extent.data.data() + output_offset, data.data() + data_start, write_n); + + data_start += write_n; + output_offset = 0; + } + + if (data_start == data.size()) { + break; + } + } + + return Error::Ok; +} + + void Box_iloc::derive_box_version() { int min_version = m_user_defined_min_version; @@ -1484,6 +1802,8 @@ void Box_iloc::derive_box_version() m_base_offset_size = 0; m_index_size = 0; + uint64_t total_data_size = 0; + for (const auto& item : m_items) { // check item_ID size if (item.item_ID > 0xFFFF) { @@ -1495,15 +1815,17 @@ void Box_iloc::derive_box_version() min_version = std::max(min_version, 1); } + total_data_size += item.extents[0].length; + + /* cannot compute this here because values are not set yet // base offset size - /* if (item.base_offset > 0xFFFFFFFF) { - m_base_offset_size = 8; + m_base_offset_size = std::max(m_base_offset_size, (uint8_t)8); } else if (item.base_offset > 0) { - m_base_offset_size = 4; + m_base_offset_size = std::max(m_base_offset_size, (uint8_t)4); } - */ +*/ /* for (const auto& extent : item.extents) { @@ -1537,9 +1859,17 @@ void Box_iloc::derive_box_version() */ } + uint64_t maximum_meta_box_size_guess = 0x10000000; // 256 MB + if (total_data_size + maximum_meta_box_size_guess > 0xFFFFFFFF) { + m_base_offset_size = 8; + } + else { + m_base_offset_size = 4; + } + m_offset_size = 4; m_length_size = 4; - m_base_offset_size = 4; // TODO: or could be 8 if we write >4GB files + //m_base_offset_size = 4; // set above m_index_size = 0; set_version((uint8_t) min_version); @@ -1617,20 +1947,28 @@ Error Box_iloc::write_mdat_after_iloc(StreamWriter& writer) for (const auto& item : m_items) { if (item.construction_method == 0) { for (const auto& extent : item.extents) { - sum_mdat_size += extent.data.size(); + sum_mdat_size += extent.length; } } } - if (sum_mdat_size > 0xFFFFFFFF) { - // TODO: box size > 4 GB - } + // --- write mdat box + if (sum_mdat_size <= 0xFFFFFFFF) { + writer.write32((uint32_t) (sum_mdat_size + 8)); + writer.write32(fourcc("mdat")); + } + else { + // box size > 4 GB - // --- write mdat box + writer.write32(1); + writer.write32(fourcc("mdat")); + writer.write64(sum_mdat_size+8+8); + } - writer.write32((uint32_t) (sum_mdat_size + 8)); - writer.write32(fourcc("mdat")); + if (m_use_tmpfile) { + ::lseek(m_tmpfile_fd, 0, SEEK_SET); + } for (auto& item : m_items) { if (item.construction_method == 0) { @@ -1638,9 +1976,38 @@ Error Box_iloc::write_mdat_after_iloc(StreamWriter& writer) for (auto& extent : item.extents) { extent.offset = writer.get_position() - item.base_offset; - extent.length = extent.data.size(); - - writer.write(extent.data); + //extent.length = extent.data.size(); + + if (m_use_tmpfile) { + std::vector data(extent.length); +#if !defined(_WIN32) + ssize_t cnt = ::read(m_tmpfile_fd, data.data(), extent.length); +#else + // TODO Currently unused code. Implement when needed. + assert(false); +# if 0 + int cnt = _read(m_tmpfile_fd, data.data(), extent.length); +# else + int cnt = -1; +# endif +#endif + if (cnt<0) { + std::stringstream sstr; + sstr << "Cannot read tmp data file, error " << errno; + return {heif_error_Encoding_error, + heif_suberror_Unspecified, + sstr.str()}; + } + else if ((uint64_t)cnt != extent.length) { + return {heif_error_Encoding_error, + heif_suberror_Unspecified, + "Tmp data could not be read completely"}; + } + writer.write(data); + } + else { + writer.write(extent.data); + } } } } @@ -1682,7 +2049,12 @@ void Box_iloc::patch_iloc_header(StreamWriter& writer) const } writer.write16(item.data_reference_index); - writer.write(m_base_offset_size, item.base_offset); + if (m_base_offset_size > 0) { + writer.write(m_base_offset_size, item.base_offset); + } + else { + assert(item.base_offset == 0); + } writer.write16((uint16_t) item.extents.size()); for (const auto& extent : item.extents) { @@ -1714,10 +2086,15 @@ void Box_iloc::patch_iloc_header(StreamWriter& writer) const * Note: HEIF does not allow version 0 and version 1 boxes ! (see 23008-12, 10.2.1) */ -Error Box_infe::parse(BitstreamRange& range) +Error Box_infe::parse(BitstreamRange& range, const heif_security_limits* limits) { parse_full_box_header(range); + // only versions 2,3 are required by HEIF + if (get_version() > 3) { + return unsupported_version_error("infe"); + } + if (get_version() <= 1) { m_item_ID = range.read16(); m_item_protection_index = range.read16(); @@ -1727,6 +2104,8 @@ Error Box_infe::parse(BitstreamRange& range) m_content_encoding = range.read_string(); } + m_item_type_4cc = 0; + if (get_version() >= 2) { m_hidden_item = (get_flags() & 1); @@ -1738,17 +2117,14 @@ Error Box_infe::parse(BitstreamRange& range) } m_item_protection_index = range.read16(); - uint32_t item_type = range.read32(); - if (item_type != 0) { - m_item_type = to_fourcc(item_type); - } + m_item_type_4cc = range.read32(); m_item_name = range.read_string(); - if (item_type == fourcc("mime")) { + if (m_item_type_4cc == fourcc("mime")) { m_content_type = range.read_string(); m_content_encoding = range.read_string(); } - else if (item_type == fourcc("uri ")) { + else if (m_item_type_4cc == fourcc("uri ")) { m_item_uri_type = range.read_string(); } } @@ -1770,7 +2146,7 @@ void Box_infe::derive_box_version() } - if (m_item_type != "") { + if (m_item_type_4cc != 0) { min_version = std::max(min_version, 2); } @@ -1813,19 +2189,14 @@ Error Box_infe::write(StreamWriter& writer) const writer.write16(m_item_protection_index); - if (m_item_type.empty()) { - writer.write32(0); - } - else { - writer.write32(from_fourcc(m_item_type.c_str())); - } + writer.write32(m_item_type_4cc); writer.write(m_item_name); - if (m_item_type == "mime") { + if (m_item_type_4cc == fourcc("mime")) { writer.write(m_content_type); writer.write(m_content_encoding); } - else if (m_item_type == "uri ") { + else if (m_item_type_4cc == fourcc("uri ")) { writer.write(m_item_uri_type); } } @@ -1843,15 +2214,15 @@ std::string Box_infe::dump(Indent& indent) const sstr << indent << "item_ID: " << m_item_ID << "\n" << indent << "item_protection_index: " << m_item_protection_index << "\n" - << indent << "item_type: " << m_item_type << "\n" + << indent << "item_type: " << fourcc_to_string(m_item_type_4cc) << "\n" << indent << "item_name: " << m_item_name << "\n"; - if (m_item_type == "mime") { + if (m_item_type_4cc == fourcc("mime")) { sstr << indent << "content_type: " << m_content_type << "\n" << indent << "content_encoding: " << m_content_encoding << "\n"; } - if (m_item_type == "uri ") { + if (m_item_type_4cc == fourcc("uri ")) { sstr << indent << "item uri type: " << m_item_uri_type << "\n"; } @@ -1861,10 +2232,15 @@ std::string Box_infe::dump(Indent& indent) const } -Error Box_iinf::parse(BitstreamRange& range) +Error Box_iinf::parse(BitstreamRange& range, const heif_security_limits* limits) { parse_full_box_header(range); + // TODO: there are several images in circulation that have an iinf version=2. We should not enforce this with a hard error. + if (false && get_version() > 1) { + return unsupported_version_error("iinf"); + } + int nEntries_size = (get_version() > 0) ? 4 : 2; uint32_t item_count; @@ -1879,8 +2255,7 @@ Error Box_iinf::parse(BitstreamRange& range) return Error::Ok; } - // TODO: Only try to read "item_count" children. - return read_children(range); + return read_children(range, item_count, limits); } @@ -1895,11 +2270,11 @@ std::string Box_iinf::dump(Indent& indent) const } -Error Box_iprp::parse(BitstreamRange& range) +Error Box_iprp::parse(BitstreamRange& range, const heif_security_limits* limits) { //parse_full_box_header(range); - return read_children(range); + return read_children(range, READ_CHILDREN_ALL, limits); } @@ -1953,11 +2328,11 @@ int Box_ipco::find_or_append_child_box(const std::shared_ptr& box) } -Error Box_ipco::parse(BitstreamRange& range) +Error Box_ipco::parse(BitstreamRange& range, const heif_security_limits* limits) { //parse_full_box_header(range); - return read_children(range); + return read_children(range, READ_CHILDREN_ALL, limits); } @@ -1966,20 +2341,24 @@ std::string Box_ipco::dump(Indent& indent) const std::ostringstream sstr; sstr << Box::dump(indent); - sstr << dump_children(indent); + sstr << dump_children(indent, true); return sstr.str(); } -Error Box_pixi::parse(BitstreamRange& range) +Error Box_pixi::parse(BitstreamRange& range, const heif_security_limits* limits) { parse_full_box_header(range); + if (get_version() != 0) { + return unsupported_version_error("pixi"); + } + StreamReader::grow_status status; uint8_t num_channels = range.read8(); status = range.wait_for_available_bytes(num_channels); - if (status != StreamReader::size_reached) { + if (status != StreamReader::grow_status::size_reached) { // TODO: return recoverable error at timeout return Error(heif_error_Invalid_input, heif_suberror_End_of_data); @@ -2033,7 +2412,7 @@ Error Box_pixi::write(StreamWriter& writer) const } -Error Box_pasp::parse(BitstreamRange& range) +Error Box_pasp::parse(BitstreamRange& range, const heif_security_limits* limits) { //parse_full_box_header(range); @@ -2069,7 +2448,7 @@ Error Box_pasp::write(StreamWriter& writer) const } -Error Box_lsel::parse(BitstreamRange& range) +Error Box_lsel::parse(BitstreamRange& range, const heif_security_limits* limits) { layer_id = range.read16(); @@ -2100,7 +2479,7 @@ Error Box_lsel::write(StreamWriter& writer) const } -Error Box_clli::parse(BitstreamRange& range) +Error Box_clli::parse(BitstreamRange& range, const heif_security_limits* limits) { //parse_full_box_header(range); @@ -2144,7 +2523,7 @@ Box_mdcv::Box_mdcv() } -Error Box_mdcv::parse(BitstreamRange& range) +Error Box_mdcv::parse(BitstreamRange& range, const heif_security_limits* limits) { //parse_full_box_header(range); @@ -2268,9 +2647,9 @@ bool Box_ipco::is_property_essential_for_item(heif_item_id itemId, { // find property index - for (int i=0;i<(int)m_children.size();i++) { + for (int i = 0; i < (int) m_children.size(); i++) { if (m_children[i] == property) { - return ipma->is_property_essential_for_item(itemId, i); + return ipma->is_property_essential_for_item(itemId, i + 1); } } @@ -2279,10 +2658,14 @@ bool Box_ipco::is_property_essential_for_item(heif_item_id itemId, } -Error Box_ispe::parse(BitstreamRange& range) +Error Box_ispe::parse(BitstreamRange& range, const heif_security_limits* limits) { parse_full_box_header(range); + if (get_version() != 0) { + return unsupported_version_error("ispe"); + } + m_image_width = range.read32(); m_image_height = range.read32(); @@ -2327,10 +2710,16 @@ bool Box_ispe::operator==(const Box& other) const } -Error Box_ipma::parse(BitstreamRange& range) +Error Box_ipma::parse(BitstreamRange& range, const heif_security_limits* limits) { parse_full_box_header(range); + // TODO: is there any specification of allowed values for the ipma version in the HEIF standards? + + if (get_version() > 1) { + return unsupported_version_error("ipma"); + } + uint32_t entry_cnt = range.read32(); for (uint32_t i = 0; i < entry_cnt && !range.error() && !range.eof(); i++) { Entry entry; @@ -2343,7 +2732,7 @@ Error Box_ipma::parse(BitstreamRange& range) int assoc_cnt = range.read8(); for (int k = 0; k < assoc_cnt; k++) { - PropertyAssociation association; + PropertyAssociation association{}; uint16_t index; if (get_flags() & 1) { @@ -2413,6 +2802,15 @@ void Box_ipma::add_property_for_item_ID(heif_item_id itemID, m_entries.push_back(entry); } + // If the property is already associated with the item, skip. + for (auto const& a : m_entries[idx].associations) { + if (a.property_index == assoc.property_index) { + return; + } + + // TODO: should we check that the essential flag matches and return an internal error if not? + } + // add the property association m_entries[idx].associations.push_back(assoc); } @@ -2509,10 +2907,14 @@ void Box_ipma::insert_entries_from_other_ipma_box(const Box_ipma& b) } -Error Box_auxC::parse(BitstreamRange& range) +Error Box_auxC::parse(BitstreamRange& range, const heif_security_limits* limits) { parse_full_box_header(range); + if (get_version() != 0) { + return unsupported_version_error("auxC"); + } + m_aux_type = range.read_string(); while (!range.eof()) { @@ -2556,7 +2958,7 @@ std::string Box_auxC::dump(Indent& indent) const } -Error Box_irot::parse(BitstreamRange& range) +Error Box_irot::parse(BitstreamRange& range, const heif_security_limits* limits) { //parse_full_box_header(range); @@ -2592,7 +2994,7 @@ std::string Box_irot::dump(Indent& indent) const } -Error Box_imir::parse(BitstreamRange& range) +Error Box_imir::parse(BitstreamRange& range, const heif_security_limits* limits) { //parse_full_box_header(range); @@ -2642,7 +3044,7 @@ std::string Box_imir::dump(Indent& indent) const } -Error Box_clap::parse(BitstreamRange& range) +Error Box_clap::parse(BitstreamRange& range, const heif_security_limits* limits) { //parse_full_box_header(range); @@ -2737,35 +3139,35 @@ double Box_clap::top(int image_height) const } -int Box_clap::left_rounded(int image_width) const +int Box_clap::left_rounded(uint32_t image_width) const { // pcX = horizOff + (width - 1)/2 // pcX ± (cleanApertureWidth - 1)/2 // left = horizOff + (width-1)/2 - (clapWidth-1)/2 - Fraction pcX = m_horizontal_offset + Fraction(image_width - 1, 2); + Fraction pcX = m_horizontal_offset + Fraction(image_width - 1U, 2U); Fraction left = pcX - (m_clean_aperture_width - 1) / 2; return left.round_down(); } -int Box_clap::right_rounded(int image_width) const +int Box_clap::right_rounded(uint32_t image_width) const { Fraction right = m_clean_aperture_width - 1 + left_rounded(image_width); return right.round(); } -int Box_clap::top_rounded(int image_height) const +int Box_clap::top_rounded(uint32_t image_height) const { - Fraction pcY = m_vertical_offset + Fraction(image_height - 1, 2); + Fraction pcY = m_vertical_offset + Fraction(image_height - 1U, 2U); Fraction top = pcY - (m_clean_aperture_height - 1) / 2; return top.round(); } -int Box_clap::bottom_rounded(int image_height) const +int Box_clap::bottom_rounded(uint32_t image_height) const { Fraction bottom = m_clean_aperture_height - 1 + top_rounded(image_height); @@ -2796,10 +3198,14 @@ void Box_clap::set(uint32_t clap_width, uint32_t clap_height, } -Error Box_iref::parse(BitstreamRange& range) +Error Box_iref::parse(BitstreamRange& range, const heif_security_limits* limits) { parse_full_box_header(range); + if (get_version() > 1) { + return unsupported_version_error("iref"); + } + while (!range.eof()) { Reference ref; @@ -2808,57 +3214,41 @@ Error Box_iref::parse(BitstreamRange& range) return err; } - if (get_version() == 0) { - ref.from_item_ID = range.read16(); - int nRefs = range.read16(); - for (int i = 0; i < nRefs; i++) { - ref.to_item_ID.push_back(range.read16()); - if (range.eof()) { - break; - } - } - } - else { - ref.from_item_ID = range.read32(); - int nRefs = range.read16(); - for (int i = 0; i < nRefs; i++) { - ref.to_item_ID.push_back(range.read32()); - if (range.eof()) { - break; - } - } + int read_len = (get_version() == 0) ? 16 : 32; + + ref.from_item_ID = static_cast(range.read_uint(read_len)); + uint16_t nRefs = range.read16(); + + if (nRefs > limits->max_items) { + std::stringstream sstr; + sstr << "Number of references in iref box (" << nRefs << ") exceeds the security limits of " << limits->max_items << " references."; + + return {heif_error_Invalid_input, + heif_suberror_Security_limit_exceeded, + sstr.str()}; } - m_references.push_back(ref); - } + for (int i = 0; i < nRefs; i++) { + if (range.eof()) { + std::stringstream sstr; + sstr << "iref box should contain " << nRefs << " references, but we can only read " << i << " references."; + return {heif_error_Invalid_input, + heif_suberror_End_of_data, + sstr.str()}; + } - // --- check number of total refs + ref.to_item_ID.push_back(static_cast(range.read_uint(read_len))); + } - size_t nTotalRefs = 0; - for (const auto& ref : m_references) { - nTotalRefs += ref.to_item_ID.size(); + m_references.push_back(ref); } - if (nTotalRefs > MAX_IREF_REFERENCES) { - return Error(heif_error_Memory_allocation_error, heif_suberror_Security_limit_exceeded, - "Number of iref references exceeds security limit."); - } // --- check for duplicate references - for (const auto& ref : m_references) { - std::set to_ids; - for (const auto to_id : ref.to_item_ID) { - if (to_ids.find(to_id) == to_ids.end()) { - to_ids.insert(to_id); - } - else { - return Error(heif_error_Invalid_input, - heif_suberror_Unspecified, - "'iref' has double references"); - } - } + if (auto error = check_for_double_references()) { + return error; } @@ -2917,6 +3307,26 @@ Error Box_iref::parse(BitstreamRange& range) } +Error Box_iref::check_for_double_references() const +{ + for (const auto& ref : m_references) { + std::set to_ids; + for (const auto to_id : ref.to_item_ID) { + if (to_ids.find(to_id) == to_ids.end()) { + to_ids.insert(to_id); + } + else { + return {heif_error_Invalid_input, + heif_suberror_Unspecified, + "'iref' has double references"}; + } + } + } + + return Error::Ok; +} + + void Box_iref::derive_box_version() { uint8_t version = 0; @@ -2941,6 +3351,10 @@ void Box_iref::derive_box_version() Error Box_iref::write(StreamWriter& writer) const { + if (auto error = check_for_double_references()) { + return error; + } + size_t box_start = reserve_box_header_space(writer); int id_size = ((get_version() == 0) ? 2 : 4); @@ -2960,7 +3374,6 @@ Error Box_iref::write(StreamWriter& writer) const } } - prepend_header(writer, box_start); return Error::Ok; @@ -3032,11 +3445,28 @@ void Box_iref::add_references(heif_item_id from_id, uint32_t type, const std::ve ref.from_item_ID = from_id; ref.to_item_ID = to_ids; + assert(to_ids.size() <= 0xFFFF); + m_references.push_back(ref); } -Error Box_idat::parse(BitstreamRange& range) +void Box_iref::overwrite_reference(heif_item_id from_id, uint32_t type, uint32_t reference_idx, heif_item_id to_item) +{ + for (auto& ref : m_references) { + if (ref.from_item_ID == from_id && ref.header.get_short_type() == type) { + assert(reference_idx >= 0 && reference_idx < ref.to_item_ID.size()); + + ref.to_item_ID[reference_idx] = to_item; + return; + } + } + + assert(false); // reference was not found +} + + +Error Box_idat::parse(BitstreamRange& range, const heif_security_limits* limits) { //parse_full_box_header(range); @@ -3075,17 +3505,19 @@ std::string Box_idat::dump(Indent& indent) const Error Box_idat::read_data(const std::shared_ptr& istr, uint64_t start, uint64_t length, - std::vector& out_data) const + std::vector& out_data, + const heif_security_limits* limits) const { // --- security check that we do not allocate too much data auto curr_size = out_data.size(); - if (MAX_MEMORY_BLOCK_SIZE - curr_size < length) { + auto max_memory_block_size = limits->max_memory_block_size; + if (max_memory_block_size && max_memory_block_size - curr_size < length) { std::stringstream sstr; sstr << "idat box contained " << length << " bytes, total memory size would be " << (curr_size + length) << " bytes, exceeding the security limit of " - << MAX_MEMORY_BLOCK_SIZE << " bytes"; + << max_memory_block_size << " bytes"; return Error(heif_error_Memory_allocation_error, heif_suberror_Security_limit_exceeded, @@ -3104,8 +3536,8 @@ Error Box_idat::read_data(const std::shared_ptr& istr, } StreamReader::grow_status status = istr->wait_for_file_size((int64_t) m_data_start_pos + start + length); - if (status == StreamReader::size_beyond_eof || - status == StreamReader::timeout) { + if (status == StreamReader::grow_status::size_beyond_eof || + status == StreamReader::grow_status::timeout) { // TODO: maybe we should introduce some 'Recoverable error' instead of 'Invalid input' return Error(heif_error_Invalid_input, heif_suberror_End_of_data); @@ -3130,67 +3562,202 @@ Error Box_idat::read_data(const std::shared_ptr& istr, } -Error Box_grpl::parse(BitstreamRange& range) +Error Box_grpl::parse(BitstreamRange& range, const heif_security_limits* limits) { //parse_full_box_header(range); - //return read_children(range); + return read_children(range, READ_CHILDREN_ALL, limits); // should we pass the parsing context 'grpl' or are the box types unique? +} - while (!range.eof()) { - EntityGroup group; - Error err = group.header.parse_header(range); - if (err != Error::Ok) { - return err; - } - err = group.header.parse_full_box_header(range); - if (err != Error::Ok) { - return err; - } +std::string Box_grpl::dump(Indent& indent) const +{ + std::ostringstream sstr; + sstr << Box::dump(indent); + sstr << dump_children(indent); + return sstr.str(); +} - group.group_id = range.read32(); - uint32_t nEntities = range.read32(); - for (uint32_t i = 0; i < nEntities; i++) { - if (range.eof()) { - break; - } - group.entity_ids.push_back(range.read32()); +Error Box_EntityToGroup::parse(BitstreamRange& range, const heif_security_limits* limits) +{ + Error err = parse_full_box_header(range); + if (err != Error::Ok) { + return err; + } + + group_id = range.read32(); + uint32_t nEntities = range.read32(); + for (uint32_t i = 0; i < nEntities; i++) { + if (range.eof()) { + std::stringstream sstr; + sstr << "entity group box should contain " << nEntities << " entities, but we can only read " << i << " entities."; + + return {heif_error_Invalid_input, + heif_suberror_End_of_data, + sstr.str()}; } - m_entity_groups.push_back(group); + entity_ids.push_back(range.read32()); } - return range.get_error(); + return Error::Ok; } -std::string Box_grpl::dump(Indent& indent) const +Error Box_EntityToGroup::write(StreamWriter& writer) const +{ + size_t box_start = reserve_box_header_space(writer); + + write_entity_group_ids(writer); + + prepend_header(writer, box_start); + + return Error::Ok; +} + + +void Box_EntityToGroup::write_entity_group_ids(StreamWriter& writer) const +{ + assert(entity_ids.size() <= 0xFFFFFFFF); + + writer.write32(group_id); + writer.write32(static_cast(entity_ids.size())); + + for (uint32_t id : entity_ids) { + writer.write32(id); + } +} + + +std::string Box_EntityToGroup::dump(Indent& indent) const { std::ostringstream sstr; sstr << Box::dump(indent); - for (const auto& group : m_entity_groups) { - sstr << indent << "group type: " << group.header.get_type_string() << "\n" - << indent << "| group id: " << group.group_id << "\n" - << indent << "| entity IDs: "; + sstr << indent << "group id: " << group_id << "\n" + << indent << "entity IDs: "; - for (uint32_t id : group.entity_ids) { - sstr << id << " "; + bool first = true; + for (uint32_t id : entity_ids) { + if (first) { + first = false; + } + else { + sstr << ' '; } - sstr << "\n"; + sstr << id; + } + + sstr << "\n"; + + return sstr.str(); +} + + +Error Box_ster::parse(BitstreamRange& range, const heif_security_limits* limits) +{ + Error err = Box_EntityToGroup::parse(range, limits); + if (err) { + return err; + } + + if (entity_ids.size() != 2) { + return {heif_error_Invalid_input, + heif_suberror_Invalid_box_size, + "'ster' entity group does not exists of exactly two images"}; + } + + return Error::Ok; +} + + +std::string Box_ster::dump(Indent& indent) const +{ + std::ostringstream sstr; + sstr << Box::dump(indent); + + sstr << indent << "group id: " << group_id << "\n" + << indent << "left image ID: " << entity_ids[0] << "\n" + << indent << "right image ID: " << entity_ids[1] << "\n"; + + return sstr.str(); +} + + + +Error Box_pymd::parse(BitstreamRange& range, const heif_security_limits* limits) +{ + Error err = Box_EntityToGroup::parse(range, limits); + if (err) { + return err; + } + + tile_size_x = range.read16(); + tile_size_y = range.read16(); + + for (size_t i = 0; i < entity_ids.size(); i++) { + LayerInfo layer{}; + layer.layer_binning = range.read16(); + layer.tiles_in_layer_row_minus1 = range.read16(); + layer.tiles_in_layer_column_minus1 = range.read16(); + + m_layer_infos.push_back(layer); + } + + return Error::Ok; +} + + +Error Box_pymd::write(StreamWriter& writer) const +{ + size_t box_start = reserve_box_header_space(writer); + + Box_EntityToGroup::write_entity_group_ids(writer); + + writer.write16(tile_size_x); + writer.write16(tile_size_y); + + for (size_t i = 0; i < entity_ids.size(); i++) { + const LayerInfo& layer = m_layer_infos[i]; + + writer.write16(layer.layer_binning); + writer.write16(layer.tiles_in_layer_row_minus1); + writer.write16(layer.tiles_in_layer_column_minus1); + } + + prepend_header(writer, box_start); + + return Error::Ok; +} + + +std::string Box_pymd::dump(Indent& indent) const +{ + std::ostringstream sstr; + sstr << Box_EntityToGroup::dump(indent); + + sstr << indent << "tile size: " << tile_size_x << "x" << tile_size_y << "\n"; + + int layerNr = 0; + for (const auto& layer : m_layer_infos) { + sstr << indent << "layer " << layerNr << ":\n" + << indent << "| binning: " << layer.layer_binning << "\n" + << indent << "| tiles: " << (layer.tiles_in_layer_row_minus1 + 1) << "x" << (layer.tiles_in_layer_column_minus1 + 1) << "\n"; + + layerNr++; } return sstr.str(); } -Error Box_dinf::parse(BitstreamRange& range) +Error Box_dinf::parse(BitstreamRange& range, const heif_security_limits* limits) { //parse_full_box_header(range); - return read_children(range); + return read_children(range, READ_CHILDREN_ALL, limits); } @@ -3204,10 +3771,14 @@ std::string Box_dinf::dump(Indent& indent) const } -Error Box_dref::parse(BitstreamRange& range) +Error Box_dref::parse(BitstreamRange& range, const heif_security_limits* limits) { parse_full_box_header(range); + if (get_version() != 0) { + return unsupported_version_error("dref"); + } + uint32_t nEntities = range.read32(); /* @@ -3224,7 +3795,7 @@ Error Box_dref::parse(BitstreamRange& range) "Too many entities in dref box."); } - Error err = read_children(range, (int)nEntities); + Error err = read_children(range, (int)nEntities, limits); if (err) { return err; } @@ -3247,11 +3818,21 @@ std::string Box_dref::dump(Indent& indent) const } -Error Box_url::parse(BitstreamRange& range) +Error Box_url::parse(BitstreamRange& range, const heif_security_limits* limits) { parse_full_box_header(range); - m_location = range.read_string(); + if (get_version() > 0) { + return unsupported_version_error("url"); + } + + if (get_flags() & 1) { + // data in same file + m_location.clear(); + } + else { + m_location = range.read_string(); + } return range.get_error(); } @@ -3269,9 +3850,14 @@ std::string Box_url::dump(Indent& indent) const } -Error Box_udes::parse(BitstreamRange& range) +Error Box_udes::parse(BitstreamRange& range, const heif_security_limits* limits) { parse_full_box_header(range); + + if (get_version() > 0) { + return unsupported_version_error("udes"); + } + m_lang = range.read_string(); m_name = range.read_string(); m_description = range.read_string(); @@ -3353,10 +3939,14 @@ std::string Box_cmin::dump(Indent& indent) const } -Error Box_cmin::parse(BitstreamRange& range) +Error Box_cmin::parse(BitstreamRange& range, const heif_security_limits* limits) { parse_full_box_header(range); + if (get_version() > 0) { + return unsupported_version_error("cmin"); + } + m_denominatorShift = (get_flags() & 0x1F00) >> 8; uint32_t denominator = (1U << m_denominatorShift); @@ -3456,9 +4046,9 @@ Error Box_cmin::write(StreamWriter& writer) const } -std::array mul(const std::array& a, const std::array& b) +static std::array mul(const std::array& a, const std::array& b) { - std::array m; + std::array m{}; m[0] = a[0]*b[0] + a[1]*b[3] + a[2]*b[6]; m[1] = a[0]*b[1] + a[1]*b[4] + a[2]*b[7]; @@ -3536,10 +4126,14 @@ std::array Box_cmex::ExtrinsicMatrix::calculate_rotation_matrix() cons } -Error Box_cmex::parse(BitstreamRange& range) +Error Box_cmex::parse(BitstreamRange& range, const heif_security_limits* limits) { parse_full_box_header(range); + if (get_version() > 0) { + return unsupported_version_error("cmex"); + } + m_matrix = ExtrinsicMatrix{}; if (get_flags() & pos_x_present) { @@ -3799,7 +4393,7 @@ Error Box_taic::write(StreamWriter& writer) const { return Error::Ok; } -Error Box_taic::parse(BitstreamRange& range) { +Error Box_taic::parse(BitstreamRange& range, const heif_security_limits* limits) { parse_full_box_header(range); m_time_uncertainty = range.read64(); @@ -3834,7 +4428,7 @@ Error Box_itai::write(StreamWriter& writer) const { return Error::Ok; } -Error Box_itai::parse(BitstreamRange& range) { +Error Box_itai::parse(BitstreamRange& range, const heif_security_limits* limits) { parse_full_box_header(range); m_tai_timestamp = range.read64(); diff --git a/libheif/box.h b/libheif/box.h index ff32d85036..ae17031add 100644 --- a/libheif/box.h +++ b/libheif/box.h @@ -24,6 +24,7 @@ #include #include "common_utils.h" #include "libheif/heif.h" +#include "libheif/heif_experimental.h" #include "libheif/heif_properties.h" #include #include @@ -45,11 +46,6 @@ #define HAS_BOOL_ARRAY 1 #endif -// abbreviation -constexpr inline uint32_t fourcc(const char* id) { return fourcc_to_uint32(id); } - -std::string to_fourcc(uint32_t code); - /* constexpr uint32_t fourcc(const char* string) { @@ -157,6 +153,22 @@ class BoxHeader }; +enum class BoxStorageMode { + Undefined, + Memory, + Parsed, + File +}; + + +// Consequence of a box parse error +enum class parse_error_fatality { + fatal, // failure to parse this box leads to the associated item being unreable + ignorable, // ignoring this box will lead to unexpected output, but may be better than nothing + optional // the box contains extra information that is not essential for viewing +}; + + class Box : public BoxHeader { public: @@ -170,7 +182,7 @@ class Box : public BoxHeader // header size without the FullBox fields (if applicable) int calculate_header_size(bool data64bit) const; - static Error read(BitstreamRange& range, std::shared_ptr* box); + static Error read(BitstreamRange& range, std::shared_ptr* box, const heif_security_limits*); virtual Error write(StreamWriter& writer) const; @@ -181,19 +193,32 @@ class Box : public BoxHeader std::string dump(Indent&) const override; - std::shared_ptr get_child_box(uint32_t short_type) const; + template [[nodiscard]] std::shared_ptr get_child_box() const + { + // TODO: we could remove the dynamic_cast<> by adding the fourcc type of each Box + // as a "constexpr uint32_t Box::short_type", compare to that and use static_cast<> + for (auto& box : m_children) { + if (auto typed_box = std::dynamic_pointer_cast(box)) { + return typed_box; + } + } + + return nullptr; + } + - std::vector> get_child_boxes(uint32_t short_type) const; template - std::vector> get_typed_child_boxes(uint32_t short_type) const + std::vector> get_child_boxes() const { - auto boxes = get_child_boxes(short_type); - std::vector> typedBoxes; - for (const auto& box : boxes) { - typedBoxes.push_back(std::dynamic_pointer_cast(box)); + std::vector> result; + for (auto& box : m_children) { + if (auto typed_box = std::dynamic_pointer_cast(box)) { + result.push_back(typed_box); + } } - return typedBoxes; + + return result; } const std::vector>& get_all_child_boxes() const { return m_children; } @@ -204,23 +229,46 @@ class Box : public BoxHeader return (int) m_children.size() - 1; } + bool has_child_boxes() const { return !m_children.empty(); } + virtual bool operator==(const Box& other) const; static bool equal(const std::shared_ptr& box1, const std::shared_ptr& box2); + BoxStorageMode get_box_storage_mode() const { return m_storage_mode; } + + void set_output_position(uint64_t pos) { m_output_position = pos; } + + virtual parse_error_fatality get_parse_error_fatality() const { return parse_error_fatality::fatal; } + + virtual bool is_essential() const { return m_is_essential; } // only used for properties + + void set_is_essential(bool flag) { m_is_essential = flag; } protected: - virtual Error parse(BitstreamRange& range); + virtual Error parse(BitstreamRange& range, const heif_security_limits* limits); std::vector> m_children; - const static int READ_CHILDREN_ALL = -1; + BoxStorageMode m_storage_mode = BoxStorageMode::Undefined; + + const static uint64_t INVALID_POSITION = 0xFFFFFFFFFFFFFFFF; + + uint64_t m_input_position = INVALID_POSITION; + uint64_t m_output_position = INVALID_POSITION; + std::vector m_box_data; // including header + + std::string m_debug_box_type; - Error read_children(BitstreamRange& range, int number = READ_CHILDREN_ALL); + bool m_is_essential = false; + + const static uint32_t READ_CHILDREN_ALL = 0xFFFFFFFF; + + Error read_children(BitstreamRange& range, uint32_t number /* READ_CHILDREN_ALL */, const heif_security_limits* limits); Error write_children(StreamWriter& writer) const; - std::string dump_children(Indent&) const; + std::string dump_children(Indent&, bool with_index = false) const; // --- writing @@ -261,6 +309,7 @@ class FullBox : public Box Error write_header(StreamWriter&, size_t total_size, bool data64bit = false) const override; + Error unsupported_version_error(const char* box) const; private: uint8_t m_version = 0; @@ -285,13 +334,43 @@ class Box_other : public Box std::string dump(Indent&) const override; protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; std::vector m_data; }; +class Box_Error : public Box +{ +public: + Box_Error(uint32_t box4cc, Error err, parse_error_fatality fatality) + { + set_short_type(fourcc("ERR ")); + + m_box_type_with_parse_error = box4cc; + m_error = err; + m_fatality = fatality; + } + + Error write(StreamWriter& writer) const override { return {heif_error_Usage_error, heif_suberror_Unspecified, "Cannot write dummy error box."}; } + + std::string dump(Indent&) const override; + + [[nodiscard]] parse_error_fatality get_parse_error_fatality() const override; + + [[nodiscard]] Error get_error() const { return m_error; } + +protected: + Error parse(BitstreamRange& range, const heif_security_limits*) override { assert(false); return Error::Ok; } + + uint32_t m_box_type_with_parse_error; + Error m_error; + parse_error_fatality m_fatality; +}; + + + class Box_ftyp : public Box { @@ -318,7 +397,7 @@ class Box_ftyp : public Box Error write(StreamWriter& writer) const override; protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; private: uint32_t m_major_brand = 0; @@ -338,7 +417,7 @@ class Box_meta : public FullBox std::string dump(Indent&) const override; protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; }; @@ -361,7 +440,7 @@ class Box_hdlr : public FullBox void set_name(std::string name) { m_name = std::move(name); } protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; private: uint32_t m_pre_defined = 0; @@ -390,7 +469,7 @@ class Box_pitm : public FullBox Error write(StreamWriter& writer) const override; protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; private: heif_item_id m_item_ID = 0; @@ -400,10 +479,11 @@ class Box_pitm : public FullBox class Box_iloc : public FullBox { public: - Box_iloc() - { - set_short_type(fourcc("iloc")); - } + Box_iloc(); + + ~Box_iloc() override; + + void set_use_tmp_file(bool flag); std::string dump(Indent&) const override; @@ -428,18 +508,33 @@ class Box_iloc : public FullBox const std::vector& get_items() const { return m_items; } - Error read_data(const Item& item, + Error read_data(heif_item_id item, + const std::shared_ptr& istr, + const std::shared_ptr&, + std::vector* dest, + const heif_security_limits* limits) const; + + // Note: size==std::numeric_limits::max() reads the data until the end + Error read_data(heif_item_id item, const std::shared_ptr& istr, const std::shared_ptr&, - std::vector* dest) const; + std::vector* dest, + uint64_t offset, uint64_t size, + const heif_security_limits* limits) const; void set_min_version(uint8_t min_version) { m_user_defined_min_version = min_version; } // append bitstream data that will be written later (after iloc box) + // TODO: use an enum for the construction method Error append_data(heif_item_id item_ID, const std::vector& data, uint8_t construction_method = 0); + Error replace_data(heif_item_id item_ID, + uint64_t output_offset, + const std::vector& data, + uint8_t construction_method); + // append bitstream data that already has been written (before iloc box) // Error write_mdat_before_iloc(heif_image_id item_ID, // std::vector& data) @@ -457,7 +552,7 @@ class Box_iloc : public FullBox Error write_mdat_after_iloc(StreamWriter& writer); protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; private: std::vector m_items; @@ -472,6 +567,10 @@ class Box_iloc : public FullBox void patch_iloc_header(StreamWriter& writer) const; int m_idat_offset = 0; // only for writing: offset of next data array + + bool m_use_tmpfile = false; + int m_tmpfile_fd = 0; + char m_tmp_filename[20]; }; @@ -493,9 +592,9 @@ class Box_infe : public FullBox void set_item_ID(heif_item_id id) { m_item_ID = id; } - const std::string& get_item_type() const { return m_item_type; } + uint32_t get_item_type_4cc() const { return m_item_type_4cc; } - void set_item_type(const std::string& type) { m_item_type = type; } + void set_item_type_4cc(uint32_t type) { m_item_type_4cc = type; } void set_item_name(const std::string& name) { m_item_name = name; } @@ -518,13 +617,13 @@ class Box_infe : public FullBox void set_item_uri_type(const std::string& uritype) { m_item_uri_type = uritype; } protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; private: heif_item_id m_item_ID = 0; uint16_t m_item_protection_index = 0; - std::string m_item_type; + uint32_t m_item_type_4cc = 0; std::string m_item_name; std::string m_content_type; std::string m_content_encoding; @@ -550,7 +649,7 @@ class Box_iinf : public FullBox Error write(StreamWriter& writer) const override; protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; private: //std::vector< std::shared_ptr > m_iteminfos; @@ -568,7 +667,7 @@ class Box_iprp : public Box std::string dump(Indent&) const override; protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; }; @@ -597,7 +696,7 @@ class Box_ipco : public Box std::string dump(Indent&) const override; protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; }; @@ -625,8 +724,10 @@ class Box_ispe : public FullBox bool operator==(const Box& other) const override; + bool is_essential() const override { return false; } + protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; private: uint32_t m_image_width = 0; @@ -664,7 +765,7 @@ class Box_ipma : public FullBox void insert_entries_from_other_ipma_box(const Box_ipma& b); protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; struct Entry { @@ -690,10 +791,12 @@ class Box_auxC : public FullBox const std::vector& get_subtypes() const { return m_aux_subtypes; } + bool is_essential() const override { return true; } + std::string dump(Indent&) const override; protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; Error write(StreamWriter& writer) const override; @@ -711,15 +814,19 @@ class Box_irot : public Box set_short_type(fourcc("irot")); } + bool is_essential() const override { return true; } + std::string dump(Indent&) const override; - int get_rotation() const { return m_rotation; } + int get_rotation_ccw() const { return m_rotation; } // Only these multiples of 90 are allowed: 0, 90, 180, 270. void set_rotation_ccw(int rot) { m_rotation = rot; } + [[nodiscard]] parse_error_fatality get_parse_error_fatality() const override { return parse_error_fatality::ignorable; } + protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; Error write(StreamWriter& writer) const override; @@ -736,14 +843,18 @@ class Box_imir : public Box set_short_type(fourcc("imir")); } + bool is_essential() const override { return true; } + heif_transform_mirror_direction get_mirror_direction() const { return m_axis; } void set_mirror_direction(heif_transform_mirror_direction dir) { m_axis = dir; } std::string dump(Indent&) const override; + [[nodiscard]] parse_error_fatality get_parse_error_fatality() const override { return parse_error_fatality::ignorable; } + protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; Error write(StreamWriter& writer) const override; @@ -760,12 +871,14 @@ class Box_clap : public Box set_short_type(fourcc("clap")); } + bool is_essential() const override { return true; } + std::string dump(Indent&) const override; - int left_rounded(int image_width) const; // first column - int right_rounded(int image_width) const; // last column that is part of the cropped image - int top_rounded(int image_height) const; // first row - int bottom_rounded(int image_height) const; // last row included in the cropped image + int left_rounded(uint32_t image_width) const; // first column + int right_rounded(uint32_t image_width) const; // last column that is part of the cropped image + int top_rounded(uint32_t image_height) const; // first row + int bottom_rounded(uint32_t image_height) const; // last row included in the cropped image double left(int image_width) const; double top(int image_height) const; @@ -777,8 +890,10 @@ class Box_clap : public Box void set(uint32_t clap_width, uint32_t clap_height, uint32_t image_width, uint32_t image_height); + [[nodiscard]] parse_error_fatality get_parse_error_fatality() const override { return parse_error_fatality::ignorable; } + protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; Error write(StreamWriter& writer) const override; @@ -817,13 +932,17 @@ class Box_iref : public FullBox void add_references(heif_item_id from_id, uint32_t type, const std::vector& to_ids); + void overwrite_reference(heif_item_id from_id, uint32_t type, uint32_t reference_idx, heif_item_id to_item); + protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; Error write(StreamWriter& writer) const override; void derive_box_version() override; + Error check_for_double_references() const; + private: std::vector m_references; }; @@ -836,7 +955,8 @@ class Box_idat : public Box Error read_data(const std::shared_ptr& istr, uint64_t start, uint64_t length, - std::vector& out_data) const; + std::vector& out_data, + const heif_security_limits* limits) const; int append_data(const std::vector& data) { @@ -852,7 +972,7 @@ class Box_idat : public Box Error write(StreamWriter& writer) const override; protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; std::streampos m_data_start_pos; @@ -863,30 +983,114 @@ class Box_idat : public Box class Box_grpl : public Box { public: + Box_grpl() + { + set_short_type(fourcc("grpl")); + } + std::string dump(Indent&) const override; protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; +}; + + +class Box_EntityToGroup : public FullBox +{ +public: + std::string dump(Indent&) const override; + + Error write(StreamWriter& writer) const override; + + void set_group_id(heif_entity_group_id id) { group_id = id; } + + heif_entity_group_id get_group_id() const { return group_id; } + + void set_item_ids(const std::vector& ids) { entity_ids = ids; } + + const std::vector& get_item_ids() const { return entity_ids; } - struct EntityGroup +protected: + heif_entity_group_id group_id = 0; + std::vector entity_ids; + + Error parse(BitstreamRange& range, const heif_security_limits*) override; + + void write_entity_group_ids(StreamWriter& writer) const; +}; + + +class Box_ster : public Box_EntityToGroup +{ +public: + Box_ster() { - FullBox header; - uint32_t group_id; + set_short_type(fourcc("ster")); + } + + std::string dump(Indent&) const override; + + heif_item_id get_left_image() const { return entity_ids[0]; } + heif_item_id get_right_image() const { return entity_ids[1]; } - std::vector entity_ids; +protected: + + Error parse(BitstreamRange& range, const heif_security_limits*) override; +}; + + +class Box_pymd : public Box_EntityToGroup +{ +public: + Box_pymd() + { + set_short_type(fourcc("pymd")); + } + + std::string dump(Indent&) const override; + + Error write(StreamWriter& writer) const override; + + struct LayerInfo { + uint16_t layer_binning; + uint16_t tiles_in_layer_row_minus1; + uint16_t tiles_in_layer_column_minus1; }; - std::vector m_entity_groups; + void set_layers(uint16_t _tile_size_x, + uint16_t _tile_size_y, + const std::vector& layers, + const std::vector& layer_item_ids) // low to high resolution + { + set_item_ids(layer_item_ids); + m_layer_infos = layers; + tile_size_x = _tile_size_x; + tile_size_y = _tile_size_y; + } + + const std::vector& get_layers() const { return m_layer_infos; } + + [[nodiscard]] parse_error_fatality get_parse_error_fatality() const override { return parse_error_fatality::ignorable; } + +protected: + uint16_t tile_size_x = 0; + uint16_t tile_size_y = 0; + + std::vector m_layer_infos; + + Error parse(BitstreamRange& range, const heif_security_limits*) override; }; + + class Box_dinf : public Box { public: std::string dump(Indent&) const override; protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; }; @@ -896,7 +1100,7 @@ class Box_dref : public FullBox std::string dump(Indent&) const override; protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; }; @@ -905,8 +1109,10 @@ class Box_url : public FullBox public: std::string dump(Indent&) const override; + bool is_same_file() const { return m_location.empty(); } + protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; std::string m_location; }; @@ -932,8 +1138,10 @@ class Box_pixi : public FullBox Error write(StreamWriter& writer) const override; + [[nodiscard]] parse_error_fatality get_parse_error_fatality() const override { return parse_error_fatality::optional; } + protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; private: std::vector m_bits_per_channel; @@ -955,8 +1163,10 @@ class Box_pasp : public Box Error write(StreamWriter& writer) const override; + [[nodiscard]] parse_error_fatality get_parse_error_fatality() const override { return parse_error_fatality::optional; } + protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; }; @@ -974,8 +1184,10 @@ class Box_lsel : public Box Error write(StreamWriter& writer) const override; + [[nodiscard]] parse_error_fatality get_parse_error_fatality() const override { return parse_error_fatality::optional; } + protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; }; @@ -996,8 +1208,10 @@ class Box_clli : public Box Error write(StreamWriter& writer) const override; + [[nodiscard]] parse_error_fatality get_parse_error_fatality() const override { return parse_error_fatality::optional; } + protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; }; @@ -1012,8 +1226,10 @@ class Box_mdcv : public Box Error write(StreamWriter& writer) const override; + [[nodiscard]] parse_error_fatality get_parse_error_fatality() const override { return parse_error_fatality::optional; } + protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; }; @@ -1082,8 +1298,10 @@ class Box_cmin : public FullBox void set_intrinsic_matrix(RelativeIntrinsicMatrix matrix); + [[nodiscard]] parse_error_fatality get_parse_error_fatality() const override { return parse_error_fatality::optional; } + protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; Error write(StreamWriter& writer) const override; @@ -1135,8 +1353,10 @@ class Box_cmex : public FullBox Error set_extrinsic_matrix(ExtrinsicMatrix matrix); + [[nodiscard]] parse_error_fatality get_parse_error_fatality() const override { return parse_error_fatality::optional; } + protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; Error write(StreamWriter& writer) const override; @@ -1245,8 +1465,10 @@ class Box_udes : public FullBox */ void set_tags(const std::string tags) { m_tags = tags; } + [[nodiscard]] parse_error_fatality get_parse_error_fatality() const override { return parse_error_fatality::optional; } + protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; private: std::string m_lang; @@ -1311,7 +1533,7 @@ class Box_taic : public FullBox uint8_t get_clock_type() const { return m_clock_type; } protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; private: uint64_t m_time_uncertainty = heif_tai_clock_info_unknown_time_uncertainty; @@ -1362,7 +1584,7 @@ class Box_itai : public FullBox bool get_timestamp_is_modified() const { return m_timestamp_is_modified; } protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits*) override; private: uint64_t m_tai_timestamp; diff --git a/libheif/codecs/avc.cc b/libheif/codecs/avc.cc deleted file mode 100644 index 730613df10..0000000000 --- a/libheif/codecs/avc.cc +++ /dev/null @@ -1,144 +0,0 @@ -/* - * HEIF AVC codec. - * Copyright (c) 2023 Brad Hards - * - * 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 "avc.h" -#include -#include -#include -#include - -Error Box_avcC::parse(BitstreamRange &range) { - m_configuration.configuration_version = range.read8(); - m_configuration.AVCProfileIndication = range.read8(); - m_configuration.profile_compatibility = range.read8(); - m_configuration.AVCLevelIndication = range.read8(); - uint8_t lengthSizeMinusOneWithReserved = range.read8(); - m_configuration.lengthSize = - (lengthSizeMinusOneWithReserved & 0b00000011) + 1; - - uint8_t numOfSequenceParameterSets = (range.read8() & 0b00011111); - for (int i = 0; i < numOfSequenceParameterSets; i++) { - uint16_t sequenceParameterSetLength = range.read16(); - std::vector sps(sequenceParameterSetLength); - range.read(sps.data(), sps.size()); - m_sps.push_back(sps); - } - - uint8_t numOfPictureParameterSets = range.read8(); - for (int i = 0; i < numOfPictureParameterSets; i++) { - uint16_t pictureParameterSetLength = range.read16(); - std::vector pps(pictureParameterSetLength); - range.read(pps.data(), pps.size()); - m_pps.push_back(pps); - } - - if ((m_configuration.AVCProfileIndication != 66) && - (m_configuration.AVCProfileIndication != 77) && - (m_configuration.AVCProfileIndication != 88)) { - // TODO: we don't support this yet - } - - return range.get_error(); -} - -Error Box_avcC::write(StreamWriter &writer) const { - size_t box_start = reserve_box_header_space(writer); - - writer.write8(m_configuration.configuration_version); - writer.write8(m_configuration.AVCProfileIndication); - writer.write8(m_configuration.profile_compatibility); - writer.write8(m_configuration.AVCLevelIndication); - uint8_t lengthSizeMinusOneWithReserved = 0b11111100 | ((m_configuration.lengthSize - 1) & 0b11); - writer.write8(lengthSizeMinusOneWithReserved); - uint8_t numSpsWithReserved = 0b11100000 | (m_sps.size() & 0b00011111); - writer.write8(numSpsWithReserved); - for (const auto &sps: m_sps) { - writer.write16((uint16_t) sps.size()); - writer.write(sps); - } - writer.write8(m_pps.size() & 0xFF); - for (const auto &pps: m_pps) { - writer.write16((uint16_t) pps.size()); - writer.write(pps); - } - prepend_header(writer, box_start); - - return Error::Ok; -} - -std::string Box_avcC::dump(Indent &indent) const { - std::ostringstream sstr; - sstr << Box::dump(indent); - sstr << indent << "configuration_version: " - << ((int)m_configuration.configuration_version) << "\n" - << indent << "AVCProfileIndication: " - << ((int)m_configuration.AVCProfileIndication) << " (" - << profileIndicationAsText() << ")" - << "\n" - << indent << "profile_compatibility: " - << ((int)m_configuration.profile_compatibility) << "\n" - << indent - << "AVCLevelIndication: " << ((int)m_configuration.AVCLevelIndication) - << "\n"; - - for (const auto &sps : m_sps) { - sstr << indent << "SPS: "; - for (uint8_t b : sps) { - sstr << std::setfill('0') << std::setw(2) << std::hex << ((int)b) << " "; - } - sstr << "\n"; - sstr << std::dec; - } - - for (const auto &pps : m_pps) { - sstr << indent << "PPS: "; - for (uint8_t b : pps) { - sstr << std::setfill('0') << std::setw(2) << std::hex << ((int)b) << " "; - } - sstr << "\n"; - sstr << std::dec; - } - - return sstr.str(); -} - -std::string Box_avcC::profileIndicationAsText() const { - // See ISO/IEC 14496-10:2022 Annex A - switch (m_configuration.AVCProfileIndication) { - case 44: - return "CALVC 4:4:4"; - case 66: - return "Constrained Baseline"; - case 77: - return "Main"; - case 88: - return "Extended"; - case 100: - return "High variant"; - case 110: - return "High 10"; - case 122: - return "High 4:2:2"; - case 244: - return "High 4:4:4"; - default: - return "Unknown"; - } -} diff --git a/libheif/codecs/avc_boxes.cc b/libheif/codecs/avc_boxes.cc new file mode 100644 index 0000000000..b49d9e4b6d --- /dev/null +++ b/libheif/codecs/avc_boxes.cc @@ -0,0 +1,267 @@ +/* + * HEIF AVC codec. + * Copyright (c) 2023 Brad Hards + * + * 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 "avc_boxes.h" +#include +#include +#include +#include +#include "file.h" +#include "context.h" +#include "avc_dec.h" + + +Error Box_avcC::parse(BitstreamRange& range, const heif_security_limits* limits) +{ + m_configuration.configuration_version = range.read8(); + m_configuration.AVCProfileIndication = range.read8(); + m_configuration.profile_compatibility = range.read8(); + m_configuration.AVCLevelIndication = range.read8(); + uint8_t lengthSizeMinusOneWithReserved = range.read8(); + m_configuration.lengthSize = + (lengthSizeMinusOneWithReserved & 0b00000011) + 1; + + uint8_t numOfSequenceParameterSets = (range.read8() & 0b00011111); + for (int i = 0; i < numOfSequenceParameterSets; i++) { + uint16_t sequenceParameterSetLength = range.read16(); + std::vector sps(sequenceParameterSetLength); + range.read(sps.data(), sps.size()); + m_sps.push_back(sps); + } + + uint8_t numOfPictureParameterSets = range.read8(); + for (int i = 0; i < numOfPictureParameterSets; i++) { + uint16_t pictureParameterSetLength = range.read16(); + std::vector pps(pictureParameterSetLength); + range.read(pps.data(), pps.size()); + m_pps.push_back(pps); + } + + // See ISO/IEC 14496-15 2017 Section 5.3.3.1.2 + if ((m_configuration.AVCProfileIndication != 66) && + (m_configuration.AVCProfileIndication != 77) && + (m_configuration.AVCProfileIndication != 88)) { + m_configuration.chroma_format = (heif_chroma) (range.read8() & 0b00000011); + m_configuration.bit_depth_luma = 8 + (range.read8() & 0b00000111); + m_configuration.bit_depth_chroma = 8 + (range.read8() & 0b00000111); + uint8_t numOfSequenceParameterSetExt = range.read8(); + for (int i = 0; i < numOfSequenceParameterSetExt; i++) { + uint16_t sequenceParameterSetExtLength = range.read16(); + std::vector sps_ext(sequenceParameterSetExtLength); + range.read(sps_ext.data(), sps_ext.size()); + m_sps_ext.push_back(sps_ext); + } + } + + return range.get_error(); +} + +Error Box_avcC::write(StreamWriter& writer) const +{ + size_t box_start = reserve_box_header_space(writer); + + writer.write8(m_configuration.configuration_version); + writer.write8(m_configuration.AVCProfileIndication); + writer.write8(m_configuration.profile_compatibility); + writer.write8(m_configuration.AVCLevelIndication); + uint8_t lengthSizeMinusOneWithReserved = 0b11111100 | ((m_configuration.lengthSize - 1) & 0b11); + writer.write8(lengthSizeMinusOneWithReserved); + + if (m_sps.size() > 0b00011111) { + return {heif_error_Encoding_error, + heif_suberror_Unspecified, + "Cannot write more than 31 PPS into avcC box."}; + } + + uint8_t numSpsWithReserved = 0b11100000 | (m_sps.size() & 0b00011111); + writer.write8(numSpsWithReserved); + for (const auto& sps : m_sps) { + if (sps.size() > 0xFFFF) { + return {heif_error_Encoding_error, + heif_suberror_Unspecified, + "Cannot write SPS larger than 65535 bytes into avcC box."}; + } + writer.write16((uint16_t) sps.size()); + writer.write(sps); + } + + if (m_pps.size() > 0xFF) { + return {heif_error_Encoding_error, + heif_suberror_Unspecified, + "Cannot write more than 255 PPS into avcC box."}; + } + + writer.write8(m_pps.size() & 0xFF); + for (const auto& pps : m_pps) { + if (pps.size() > 0xFFFF) { + return {heif_error_Encoding_error, + heif_suberror_Unspecified, + "Cannot write PPS larger than 65535 bytes into avcC box."}; + } + writer.write16((uint16_t) pps.size()); + writer.write(pps); + } + + if ((m_configuration.AVCProfileIndication != 66) && + (m_configuration.AVCProfileIndication != 77) && + (m_configuration.AVCProfileIndication != 88)) { + writer.write8(m_configuration.chroma_format); + writer.write8(m_configuration.bit_depth_luma - 8); + writer.write8(m_configuration.bit_depth_chroma - 8); + + if (m_sps_ext.size() > 0xFF) { + return {heif_error_Encoding_error, + heif_suberror_Unspecified, + "Cannot write more than 255 SPS-Ext into avcC box."}; + } + + writer.write8(m_sps_ext.size() & 0xFF); + for (const auto& spsext : m_sps_ext) { + if (spsext.size() > 0xFFFF) { + return {heif_error_Encoding_error, + heif_suberror_Unspecified, + "Cannot write SPS-Ext larger than 65535 bytes into avcC box."}; + } + writer.write16((uint16_t) spsext.size()); + writer.write(spsext); + } + } + + prepend_header(writer, box_start); + + return Error::Ok; +} + +std::string Box_avcC::dump(Indent& indent) const +{ + std::ostringstream sstr; + sstr << Box::dump(indent); + sstr << indent << "configuration_version: " << ((int) m_configuration.configuration_version) << "\n" + << indent << "AVCProfileIndication: " << ((int) m_configuration.AVCProfileIndication) << " (" << profileIndicationAsText() << ")\n" + << indent << "profile_compatibility: " << ((int) m_configuration.profile_compatibility) << "\n" + << indent << "AVCLevelIndication: " << ((int) m_configuration.AVCLevelIndication) << "\n" + << indent << "Chroma format: "; + + switch (m_configuration.chroma_format) { + case heif_chroma_monochrome: + sstr << "4:0:0\n"; + break; + case heif_chroma_420: + sstr << "4:2:0\n"; + break; + case heif_chroma_422: + sstr << "4:2:2\n"; + break; + case heif_chroma_444: + sstr << "4:4:4\n"; + break; + default: + sstr << "unsupported\n"; + break; + } + + sstr << indent << "Bit depth luma: " << ((int) m_configuration.bit_depth_luma) << "\n" + << indent << "Bit depth chroma: " << ((int) m_configuration.bit_depth_chroma) << "\n"; + + for (const auto& sps : m_sps) { + sstr << indent << "SPS: "; + for (uint8_t b : sps) { + sstr << std::setfill('0') << std::setw(2) << std::hex << ((int) b) << " "; + } + sstr << "\n"; + sstr << std::dec; + } + + for (const auto& spsext : m_sps_ext) { + sstr << indent << "SPS-EXT: "; + for (uint8_t b : spsext) { + sstr << std::setfill('0') << std::setw(2) << std::hex << ((int) b) << " "; + } + sstr << "\n"; + sstr << std::dec; + } + + for (const auto& pps : m_pps) { + sstr << indent << "PPS: "; + for (uint8_t b : pps) { + sstr << std::setfill('0') << std::setw(2) << std::hex << ((int) b) << " "; + } + sstr << "\n"; + sstr << std::dec; + } + + return sstr.str(); +} + +std::string Box_avcC::profileIndicationAsText() const +{ + // See ISO/IEC 14496-10:2022 Annex A + switch (m_configuration.AVCProfileIndication) { + case 44: + return "CALVC 4:4:4"; + case 66: + return "Constrained Baseline"; + case 77: + return "Main"; + case 88: + return "Extended"; + case 100: + return "High variant"; + case 110: + return "High 10"; + case 122: + return "High 4:2:2"; + case 244: + return "High 4:4:4"; + default: + return "Unknown"; + } +} + + +void Box_avcC::get_header_nals(std::vector& data) const +{ + for (const auto& sps : m_sps) { + data.push_back((sps.size() >> 24) & 0xFF); + data.push_back((sps.size() >> 16) & 0xFF); + data.push_back((sps.size() >> 8) & 0xFF); + data.push_back((sps.size() >> 0) & 0xFF); + + data.insert(data.end(), sps.begin(), sps.end()); + } + + for (const auto& spsext : m_sps_ext) { + data.push_back((spsext.size() >> 24) & 0xFF); + data.push_back((spsext.size() >> 16) & 0xFF); + data.push_back((spsext.size() >> 8) & 0xFF); + data.push_back((spsext.size() >> 0) & 0xFF); + + data.insert(data.end(), spsext.begin(), spsext.end()); + } + + for (const auto& pps : m_pps) { + data.push_back((pps.size() >> 24) & 0xFF); + data.push_back((pps.size() >> 16) & 0xFF); + data.push_back((pps.size() >> 8) & 0xFF); + data.push_back((pps.size() >> 0) & 0xFF); + + data.insert(data.end(), pps.begin(), pps.end()); + } +} diff --git a/libheif/codecs/avc.h b/libheif/codecs/avc_boxes.h similarity index 74% rename from libheif/codecs/avc.h rename to libheif/codecs/avc_boxes.h index 5f3c826a3a..983ab0c2e0 100644 --- a/libheif/codecs/avc.h +++ b/libheif/codecs/avc_boxes.h @@ -18,25 +18,33 @@ * along with libheif. If not, see . */ -#ifndef HEIF_AVC_H -#define HEIF_AVC_H +#ifndef HEIF_AVC_BOXES_H +#define HEIF_AVC_BOXES_H #include "box.h" #include "error.h" #include #include #include +#include +#include "image-items/image_item.h" + class Box_avcC : public Box { public: Box_avcC() { set_short_type(fourcc("avcC")); } + bool is_essential() const override { return true; } + struct configuration { uint8_t configuration_version; uint8_t AVCProfileIndication; // profile_idc uint8_t profile_compatibility; // constraint set flags uint8_t AVCLevelIndication; // level_idc uint8_t lengthSize; + heif_chroma chroma_format = heif_chroma_420; // Note: avcC integer value can be cast to heif_chroma enum + uint8_t bit_depth_luma = 8; + uint8_t bit_depth_chroma = 8; }; void set_configuration(const configuration& config) @@ -59,13 +67,19 @@ class Box_avcC : public Box { return m_pps; } + const std::vector< std::vector > getSequenceParameterSetExt() const + { + return m_sps_ext; + } + + void get_header_nals(std::vector& data) const; std::string dump(Indent &) const override; Error write(StreamWriter &writer) const override; protected: - Error parse(BitstreamRange &range) override; + Error parse(BitstreamRange &range, const heif_security_limits* limits) override; std::string profileIndicationAsText() const; @@ -73,6 +87,7 @@ class Box_avcC : public Box { configuration m_configuration; std::vector< std::vector > m_sps; std::vector< std::vector > m_pps; + std::vector< std::vector > m_sps_ext; }; #endif diff --git a/libheif/codecs/avc_dec.cc b/libheif/codecs/avc_dec.cc new file mode 100644 index 0000000000..8af79a4c6a --- /dev/null +++ b/libheif/codecs/avc_dec.cc @@ -0,0 +1,62 @@ +/* + * HEIF codec. + * Copyright (c) 2024 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 "avc_dec.h" +#include "avc_boxes.h" +#include "error.h" +#include "context.h" + +#include + + +Result> Decoder_AVC::read_bitstream_configuration_data() const +{ + std::vector data; + m_avcC->get_header_nals(data); + + return data; +} + + +int Decoder_AVC::get_luma_bits_per_pixel() const +{ + return m_avcC->get_configuration().bit_depth_luma; +} + + +int Decoder_AVC::get_chroma_bits_per_pixel() const +{ + return m_avcC->get_configuration().bit_depth_chroma; +} + + +Error Decoder_AVC::get_coded_image_colorspace(heif_colorspace* out_colorspace, heif_chroma* out_chroma) const +{ + *out_chroma = m_avcC->get_configuration().chroma_format; + + if (*out_chroma == heif_chroma_monochrome) { + *out_colorspace = heif_colorspace_monochrome; + } + else { + *out_colorspace = heif_colorspace_YCbCr; + } + + return Error::Ok; +} diff --git a/libheif/codecs/avc_dec.h b/libheif/codecs/avc_dec.h new file mode 100644 index 0000000000..3d08653b6b --- /dev/null +++ b/libheif/codecs/avc_dec.h @@ -0,0 +1,53 @@ +/* + * HEIF codec. + * Copyright (c) 2024 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 HEIF_AVC_DEC_H +#define HEIF_AVC_DEC_H + +#include "libheif/heif.h" +#include "error.h" + +#include +#include +#include + +class Box_avcC; + + +class Decoder_AVC : public Decoder +{ +public: + explicit Decoder_AVC(const std::shared_ptr& avcC) : m_avcC(avcC) {} + + heif_compression_format get_compression_format() const override { return heif_compression_AVC; } + + int get_luma_bits_per_pixel() const override; + + int get_chroma_bits_per_pixel() const override; + + Error get_coded_image_colorspace(heif_colorspace*, heif_chroma*) const override; + + Result> read_bitstream_configuration_data() const override; + +private: + const std::shared_ptr m_avcC; +}; + +#endif diff --git a/libheif/codecs/avif.cc b/libheif/codecs/avif_boxes.cc similarity index 97% rename from libheif/codecs/avif.cc rename to libheif/codecs/avif_boxes.cc index 33fc507f4c..584a5072b2 100644 --- a/libheif/codecs/avif.cc +++ b/libheif/codecs/avif_boxes.cc @@ -19,17 +19,21 @@ */ #include "pixelimage.h" -#include "avif.h" +#include "avif_boxes.h" +#include "avif_dec.h" #include "bitstream.h" #include "common_utils.h" +#include "libheif/api_structs.h" +#include "file.h" #include #include #include +#include // https://aomediacodec.github.io/av1-spec/av1-spec.pdf -Error Box_av1C::parse(BitstreamRange& range) +Error Box_av1C::parse(BitstreamRange& range, const heif_security_limits* limits) { //parse_full_box_header(range); @@ -185,7 +189,7 @@ Error fill_av1C_configuration(Box_av1C::configuration* inout_config, const std:: } -Error Box_a1op::parse(BitstreamRange& range) +Error Box_a1op::parse(BitstreamRange& range, const heif_security_limits* limits) { op_index = range.read8(); @@ -216,7 +220,7 @@ Error Box_a1op::write(StreamWriter& writer) const } -Error Box_a1lx::parse(BitstreamRange& range) +Error Box_a1lx::parse(BitstreamRange& range, const heif_security_limits* limits) { uint8_t flags = range.read8(); diff --git a/libheif/codecs/avif.h b/libheif/codecs/avif_boxes.h similarity index 82% rename from libheif/codecs/avif.h rename to libheif/codecs/avif_boxes.h index 7805ff5c54..b233c6c2d7 100644 --- a/libheif/codecs/avif.h +++ b/libheif/codecs/avif_boxes.h @@ -18,8 +18,8 @@ * along with libheif. If not, see . */ -#ifndef HEIF_AVIF_H -#define HEIF_AVIF_H +#ifndef HEIF_AVIF_BOXES_H +#define HEIF_AVIF_BOXES_H #include #include @@ -30,7 +30,7 @@ #include "libheif/heif.h" #include "box.h" #include "error.h" - +#include "image-items/image_item.h" class Box_av1C : public Box @@ -41,6 +41,8 @@ class Box_av1C : public Box set_short_type(fourcc("av1C")); } + bool is_essential() const override { return true; } + struct configuration { //unsigned int (1) marker = 1; @@ -51,8 +53,8 @@ class Box_av1C : public Box uint8_t high_bitdepth = 0; uint8_t twelve_bit = 0; uint8_t monochrome = 0; - uint8_t chroma_subsampling_x = 0; - uint8_t chroma_subsampling_y = 0; + uint8_t chroma_subsampling_x = 0; // (minus 1) that is: either 0 or 1 + uint8_t chroma_subsampling_y = 0; // (minus 1) uint8_t chroma_sample_position = 0; //uint8_t reserved = 0; @@ -62,13 +64,13 @@ class Box_av1C : public Box //unsigned int (8)[] configOBUs; heif_chroma get_heif_chroma() const { - if (chroma_subsampling_x==2 && chroma_subsampling_y==2) { + if (chroma_subsampling_x==1 && chroma_subsampling_y==1) { return heif_chroma_420; } - else if (chroma_subsampling_x==2 && chroma_subsampling_y==1) { + else if (chroma_subsampling_x==1 && chroma_subsampling_y==0) { return heif_chroma_422; } - else if (chroma_subsampling_x==1 && chroma_subsampling_y==1) { + else if (chroma_subsampling_x==0 && chroma_subsampling_y==0) { return heif_chroma_444; } else { @@ -96,7 +98,7 @@ class Box_av1C : public Box Error write(StreamWriter& writer) const override; protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits* limits) override; private: configuration m_configuration; @@ -120,7 +122,7 @@ class Box_a1op : public Box Error write(StreamWriter& writer) const override; protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits* limits) override; }; @@ -139,7 +141,7 @@ class Box_a1lx : public Box Error write(StreamWriter& writer) const override; protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits* limits) override; }; @@ -149,5 +151,4 @@ Error fill_av1C_configuration(Box_av1C::configuration* inout_config, const std:: bool fill_av1C_configuration_from_stream(Box_av1C::configuration* out_config, const uint8_t* data, int dataSize); - #endif diff --git a/libheif/codecs/avif_dec.cc b/libheif/codecs/avif_dec.cc new file mode 100644 index 0000000000..e0446a2de6 --- /dev/null +++ b/libheif/codecs/avif_dec.cc @@ -0,0 +1,74 @@ +/* + * HEIF codec. + * Copyright (c) 2024 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 "avif_dec.h" +#include "avif_boxes.h" +#include "error.h" +#include "context.h" + +#include + + +Result> Decoder_AVIF::read_bitstream_configuration_data() const +{ + std::vector data; + if (!m_av1C->get_headers(&data)) { + return Error{heif_error_Invalid_input, + heif_suberror_No_item_data}; + } + + return data; +} + + +int Decoder_AVIF::get_luma_bits_per_pixel() const +{ + Box_av1C::configuration config = m_av1C->get_configuration(); + if (!config.high_bitdepth) { + return 8; + } + else if (config.twelve_bit) { + return 12; + } + else { + return 10; + } +} + + +int Decoder_AVIF::get_chroma_bits_per_pixel() const +{ + return get_luma_bits_per_pixel(); +} + + +Error Decoder_AVIF::get_coded_image_colorspace(heif_colorspace* out_colorspace, heif_chroma* out_chroma) const +{ + *out_chroma = (heif_chroma) (m_av1C->get_configuration().get_heif_chroma()); + + if (*out_chroma == heif_chroma_monochrome) { + *out_colorspace = heif_colorspace_monochrome; + } + else { + *out_colorspace = heif_colorspace_YCbCr; + } + + return Error::Ok; +} diff --git a/libheif/codecs/avif_dec.h b/libheif/codecs/avif_dec.h new file mode 100644 index 0000000000..441f1864ac --- /dev/null +++ b/libheif/codecs/avif_dec.h @@ -0,0 +1,53 @@ +/* + * HEIF codec. + * Copyright (c) 2024 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 HEIF_AVIF_DEC_H +#define HEIF_AVIF_DEC_H + +#include "libheif/heif.h" +#include "error.h" + +#include +#include +#include + +class Box_av1C; + + +class Decoder_AVIF : public Decoder +{ +public: + explicit Decoder_AVIF(const std::shared_ptr& av1C) : m_av1C(av1C) {} + + heif_compression_format get_compression_format() const override { return heif_compression_AV1; } + + int get_luma_bits_per_pixel() const override; + + int get_chroma_bits_per_pixel() const override; + + Error get_coded_image_colorspace(heif_colorspace*, heif_chroma*) const override; + + Result> read_bitstream_configuration_data() const override; + +private: + const std::shared_ptr m_av1C; +}; + +#endif diff --git a/libheif/codecs/decoder.cc b/libheif/codecs/decoder.cc new file mode 100644 index 0000000000..a1568ba209 --- /dev/null +++ b/libheif/codecs/decoder.cc @@ -0,0 +1,222 @@ +/* + * HEIF codec. + * Copyright (c) 2024 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 "codecs/decoder.h" +#include "error.h" +#include "context.h" +#include "plugin_registry.h" +#include "libheif/api_structs.h" + +#include "codecs/hevc_dec.h" +#include "codecs/avif_dec.h" +#include "codecs/avc_dec.h" +#include "codecs/vvc_dec.h" +#include "codecs/jpeg_dec.h" +#include "codecs/jpeg2000_dec.h" +#include "avc_boxes.h" +#include "avif_boxes.h" +#include "hevc_boxes.h" +#include "vvc_boxes.h" +#include "jpeg_boxes.h" +#include "jpeg2000_boxes.h" +#include "codecs/uncompressed/unc_dec.h" + + +void DataExtent::set_from_image_item(std::shared_ptr file, heif_item_id item) +{ + m_file = file; + m_item_id = item; + m_source = Source::Image; +} + + +Result*> DataExtent::read_data() const +{ + if (!m_raw.empty()) { + return &m_raw; + } + else if (m_source == Source::Image) { + assert(m_file); + + // image + Error err = m_file->append_data_from_iloc(m_item_id, m_raw); + if (err) { + return err; + } + } + else { + // sequence + assert(false); // TODO + } + + return &m_raw; +} + + +Result> DataExtent::read_data(uint64_t offset, uint64_t size) const +{ + std::vector data; + + if (!m_raw.empty()) { + data.insert(data.begin(), m_raw.begin() + offset, m_raw.begin() + offset + size); + return data; + } + else if (m_source == Source::Image) { + // TODO: cache data + + // image + Error err = m_file->append_data_from_iloc(m_item_id, m_raw, 0, size); + if (err) { + return err; + } + return data; + } + else { + // sequence + assert(false); // TODO + return Error::Ok; + } +} + + +std::shared_ptr Decoder::alloc_for_infe_type(const HeifContext* ctx, heif_item_id id, uint32_t format_4cc) +{ + switch (format_4cc) { + case fourcc("hvc1"): { + auto hvcC = ctx->get_heif_file()->get_property(id); + return std::make_shared(hvcC); + } + case fourcc("av01"): { + auto av1C = ctx->get_heif_file()->get_property(id); + return std::make_shared(av1C); + } + case fourcc("avc1"): { + auto avcC = ctx->get_heif_file()->get_property(id); + return std::make_shared(avcC); + } + case fourcc("j2k1"): { + auto j2kH = ctx->get_heif_file()->get_property(id); + return std::make_shared(j2kH); + } + case fourcc("vvc1"): { + auto vvcC = ctx->get_heif_file()->get_property(id); + return std::make_shared(vvcC); + } + case fourcc("jpeg"): { + auto jpgC = ctx->get_heif_file()->get_property(id); + return std::make_shared(jpgC); + } +#if WITH_UNCOMPRESSED_CODEC + case fourcc("unci"): { + auto uncC = ctx->get_heif_file()->get_property(id); + auto cmpd = ctx->get_heif_file()->get_property(id); + return std::make_shared(uncC,cmpd); + } +#endif + case fourcc("mski"): { + return nullptr; // do we need a decoder for this? + } + default: + return nullptr; + } +} + + +Result> Decoder::get_compressed_data() const +{ + // --- get the compressed image data + + // data from configuration blocks + + Result> confData = read_bitstream_configuration_data(); + if (confData.error) { + return confData.error; + } + + std::vector data = confData.value; + + // append image data + + Result dataResult = m_data_extent.read_data(); + if (dataResult.error) { + return dataResult.error; + } + + data.insert(data.end(), dataResult.value->begin(), dataResult.value->end()); + + return data; +} + + +Result> +Decoder::decode_single_frame_from_compressed_data(const struct heif_decoding_options& options) +{ + const struct heif_decoder_plugin* decoder_plugin = get_decoder(get_compression_format(), options.decoder_id); + if (!decoder_plugin) { + return Error(heif_error_Plugin_loading_error, heif_suberror_No_matching_decoder_installed); + } + + + // --- decode image with the plugin + + void* decoder; + struct heif_error err = decoder_plugin->new_decoder(&decoder); + if (err.code != heif_error_Ok) { + return Error(err.code, err.subcode, err.message); + } + + // automatically delete decoder plugin when we leave the scope + std::unique_ptr decoderSmartPtr(decoder, decoder_plugin->free_decoder); + + if (decoder_plugin->plugin_api_version >= 2) { + if (decoder_plugin->set_strict_decoding) { + decoder_plugin->set_strict_decoding(decoder, options.strict_decoding); + } + } + + auto dataResult = get_compressed_data(); + if (dataResult.error) { + return dataResult.error; + } + + err = decoder_plugin->push_data(decoder, dataResult.value.data(), dataResult.value.size()); + if (err.code != heif_error_Ok) { + return Error(err.code, err.subcode, err.message); + } + + heif_image* decoded_img = nullptr; + + err = decoder_plugin->decode_image(decoder, &decoded_img); + if (err.code != heif_error_Ok) { + return Error(err.code, err.subcode, err.message); + } + + if (!decoded_img) { + // TODO(farindk): The plugin should return an error in this case. + return Error(heif_error_Decoder_plugin_error, heif_suberror_Unspecified); + } + + // -- cleanup + + std::shared_ptr img = std::move(decoded_img->image); + heif_image_release(decoded_img); + + return img; +} diff --git a/libheif/codecs/decoder.h b/libheif/codecs/decoder.h new file mode 100644 index 0000000000..f6d82b4a4b --- /dev/null +++ b/libheif/codecs/decoder.h @@ -0,0 +1,96 @@ +/* + * HEIF codec. + * Copyright (c) 2024 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 HEIF_DECODER_H +#define HEIF_DECODER_H + +#include "libheif/heif.h" +#include "box.h" +#include "error.h" +#include "file.h" + +#include +#include +#include +#include +#include "image-items/hevc.h" + + +// Specifies the input data for decoding. +// For images, this points to the iloc extents. +// For sequences, this points to the track data. +struct DataExtent +{ + std::shared_ptr m_file; + enum class Source : uint8_t { Raw, Image, Sequence } m_source = Source::Raw; + + // --- raw data + mutable std::vector m_raw; // also for cached data + + // --- image + heif_item_id m_item_id = 0; + + // --- sequence + // TODO + + void set_from_image_item(std::shared_ptr file, heif_item_id item); + + Result*> read_data() const; + + Result> read_data(uint64_t offset, uint64_t size) const; +}; + + +class Decoder +{ +public: + static std::shared_ptr alloc_for_infe_type(const HeifContext* ctx, heif_item_id, uint32_t format_4cc); + + + virtual ~Decoder() = default; + + virtual heif_compression_format get_compression_format() const = 0; + + void set_data_extent(DataExtent extent) { m_data_extent = std::move(extent); } + + // --- information about the image format + + [[nodiscard]] virtual int get_luma_bits_per_pixel() const = 0; + + [[nodiscard]] virtual int get_chroma_bits_per_pixel() const = 0; + + [[nodiscard]] virtual Error get_coded_image_colorspace(heif_colorspace*, heif_chroma*) const = 0; + + // --- raw data access + + [[nodiscard]] virtual Result> read_bitstream_configuration_data() const = 0; + + Result> get_compressed_data() const; + + // --- decoding + + virtual Result> + decode_single_frame_from_compressed_data(const struct heif_decoding_options& options); + +private: + DataExtent m_data_extent; +}; + +#endif diff --git a/libheif/codecs/hevc.h b/libheif/codecs/hevc.h deleted file mode 100644 index 3d2a0c3e88..0000000000 --- a/libheif/codecs/hevc.h +++ /dev/null @@ -1,121 +0,0 @@ -/* - * HEIF codec. - * Copyright (c) 2017 Dirk Farin - * - * This file is part of libheif. - * - * libheif is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as - * published by the Free Software Foundation, either version 3 of - * the License, or (at your option) any later version. - * - * libheif is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with libheif. If not, see . - */ - -#ifndef HEIF_HEVC_H -#define HEIF_HEVC_H - -#include "libheif/heif.h" -#include "box.h" -#include "error.h" - -#include -#include -#include - - - class Box_hvcC : public Box - { - public: - Box_hvcC() - { - set_short_type(fourcc("hvcC")); - } - - struct configuration - { - uint8_t configuration_version; - uint8_t general_profile_space; - bool general_tier_flag; - uint8_t general_profile_idc; - uint32_t general_profile_compatibility_flags; - - static const int NUM_CONSTRAINT_INDICATOR_FLAGS = 48; - std::bitset general_constraint_indicator_flags; - - uint8_t general_level_idc; - - uint16_t min_spatial_segmentation_idc; - uint8_t parallelism_type; - uint8_t chroma_format; - uint8_t bit_depth_luma; - uint8_t bit_depth_chroma; - uint16_t avg_frame_rate; - - uint8_t constant_frame_rate; - uint8_t num_temporal_layers; - uint8_t temporal_id_nested; - }; - - - std::string dump(Indent&) const override; - - bool get_headers(std::vector* dest) const; - - void set_configuration(const configuration& config) { m_configuration = config; } - - const configuration& get_configuration() const { return m_configuration; } - - void append_nal_data(const std::vector& nal); - - void append_nal_data(const uint8_t* data, size_t size); - - Error write(StreamWriter& writer) const override; - - protected: - Error parse(BitstreamRange& range) override; - - private: - struct NalArray - { - uint8_t m_array_completeness; - uint8_t m_NAL_unit_type; - - std::vector > m_nal_units; - }; - - configuration m_configuration; - uint8_t m_length_size = 4; // default: 4 bytes for NAL unit lengths - - std::vector m_nal_array; - }; - - class SEIMessage - { - public: - virtual ~SEIMessage() = default; - }; - - - class SEIMessage_depth_representation_info : public SEIMessage, - public heif_depth_representation_info - { - public: - }; - - - Error decode_hevc_aux_sei_messages(const std::vector& data, - std::vector>& msgs); - - - Error parse_sps_for_hvcC_configuration(const uint8_t* sps, size_t size, - Box_hvcC::configuration* inout_config, - int* width, int* height); - -#endif diff --git a/libheif/codecs/hevc.cc b/libheif/codecs/hevc_boxes.cc similarity index 89% rename from libheif/codecs/hevc.cc rename to libheif/codecs/hevc_boxes.cc index 4c23510a22..e568cdddcc 100644 --- a/libheif/codecs/hevc.cc +++ b/libheif/codecs/hevc_boxes.cc @@ -18,9 +18,11 @@ * along with libheif. If not, see . */ -#include "hevc.h" +#include "hevc_boxes.h" #include "bitstream.h" #include "error.h" +#include "file.h" +#include "hevc_dec.h" #include #include @@ -28,8 +30,10 @@ #include #include #include +#include -Error Box_hvcC::parse(BitstreamRange& range) + +Error Box_hvcC::parse(BitstreamRange& range, const heif_security_limits* limits) { //parse_full_box_header(range); @@ -316,9 +320,9 @@ Error Box_hvcC::write(StreamWriter& writer) const static double read_depth_rep_info_element(BitReader& reader) { - int sign_flag = reader.get_bits(1); + uint8_t sign_flag = reader.get_bits8(1); int exponent = reader.get_bits(7); - int mantissa_len = reader.get_bits(5) + 1; + auto mantissa_len = static_cast(reader.get_bits8(5) + 1); if (mantissa_len < 1 || mantissa_len > 32) { // TODO err } @@ -327,11 +331,12 @@ static double read_depth_rep_info_element(BitReader& reader) // TODO value unspecified } - int mantissa = reader.get_bits(mantissa_len); + uint32_t mantissa = reader.get_bits32(mantissa_len); double value; //printf("sign:%d exponent:%d mantissa_len:%d mantissa:%d\n",sign_flag,exponent,mantissa_len,mantissa); + // TODO: this seems to be wrong. 'exponent' is never negative. How to read it correctly? if (exponent > 0) { value = pow(2.0, exponent - 31) * (1.0 + mantissa / pow(2.0, mantissa_len)); } @@ -427,7 +432,13 @@ Error decode_hevc_aux_sei_messages(const std::vector& data, // Read this and the NAL size directly on the array data. BitReader reader(data.data(), (int) data.size()); - uint32_t len = (uint32_t) reader.get_bits(32); + if (reader.get_bits_remaining() < 32) { + return {heif_error_Invalid_input, + heif_suberror_End_of_data, + "HEVC SEI NAL too short"}; + } + + uint32_t len = reader.get_bits32(32); if (len > data.size() - 4) { // ERROR: read past end of data @@ -435,12 +446,19 @@ Error decode_hevc_aux_sei_messages(const std::vector& data, while (reader.get_current_byte_index() < (int) len) { int currPos = reader.get_current_byte_index(); + BitReader sei_reader(data.data() + currPos, (int) data.size() - currPos); - uint32_t nal_size = (uint32_t) sei_reader.get_bits(32); + if (sei_reader.get_bits_remaining() < 32+8) { + return {heif_error_Invalid_input, + heif_suberror_End_of_data, + "HEVC SEI NAL too short"}; + } + + uint32_t nal_size = sei_reader.get_bits32(32); (void) nal_size; - uint8_t nal_type = (uint8_t) (sei_reader.get_bits(8) >> 1); + auto nal_type = static_cast(sei_reader.get_bits8(8) >> 1); sei_reader.skip_bits(8); // SEI @@ -448,20 +466,25 @@ Error decode_hevc_aux_sei_messages(const std::vector& data, if (nal_type == 39 || nal_type == 40) { + if (sei_reader.get_bits_remaining() < 16) { + return {heif_error_Invalid_input, + heif_suberror_End_of_data, + "HEVC SEI NAL too short"}; + } + // TODO: loading of multi-byte sei headers - uint8_t payload_id = (uint8_t) (sei_reader.get_bits(8)); - uint8_t payload_size = (uint8_t) (sei_reader.get_bits(8)); + uint8_t payload_id = sei_reader.get_bits8(8); + uint8_t payload_size = sei_reader.get_bits8(8); (void) payload_size; - switch (payload_id) { - case 177: // depth_representation_info - Result> seiResult = read_depth_representation_info(sei_reader); - if (seiResult.error) { - return seiResult.error; - } + if (payload_id == 177) { + // depth_representation_info + Result> seiResult = read_depth_representation_info(sei_reader); + if (seiResult.error) { + return seiResult.error; + } - msgs.push_back(seiResult.value); - break; + msgs.push_back(seiResult.value); } } @@ -515,22 +538,22 @@ Error parse_sps_for_hvcC_configuration(const uint8_t* sps, size_t size, // skip VPS ID reader.skip_bits(4); - int nMaxSubLayersMinus1 = reader.get_bits(3); + uint8_t nMaxSubLayersMinus1 = reader.get_bits8(3); - config->temporal_id_nested = (uint8_t) reader.get_bits(1); + config->temporal_id_nested = reader.get_bits8(1); // --- profile_tier_level --- - config->general_profile_space = (uint8_t) reader.get_bits(2); - config->general_tier_flag = (uint8_t) reader.get_bits(1); - config->general_profile_idc = (uint8_t) reader.get_bits(5); - config->general_profile_compatibility_flags = reader.get_bits(32); + config->general_profile_space = reader.get_bits8(2); + config->general_tier_flag = reader.get_bits8(1); + config->general_profile_idc = reader.get_bits8(5); + config->general_profile_compatibility_flags = reader.get_bits32(32); reader.skip_bits(16); // skip reserved bits reader.skip_bits(16); // skip reserved bits reader.skip_bits(16); // skip reserved bits - config->general_level_idc = (uint8_t) reader.get_bits(8); + config->general_level_idc = reader.get_bits8(8); std::vector layer_profile_present(nMaxSubLayersMinus1); std::vector layer_level_present(nMaxSubLayersMinus1); diff --git a/libheif/codecs/hevc_boxes.h b/libheif/codecs/hevc_boxes.h new file mode 100644 index 0000000000..9b2a4971e2 --- /dev/null +++ b/libheif/codecs/hevc_boxes.h @@ -0,0 +1,124 @@ +/* + * HEIF codec. + * Copyright (c) 2017 Dirk Farin + * + * This file is part of libheif. + * + * libheif is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * libheif is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libheif. If not, see . + */ + +#ifndef HEIF_HEVC_BOXES_H +#define HEIF_HEVC_BOXES_H + +#include "libheif/heif.h" +#include "box.h" +#include "error.h" + +#include +#include +#include +#include "image-items/image_item.h" + + +class Box_hvcC : public Box +{ +public: + Box_hvcC() + { + set_short_type(fourcc("hvcC")); + } + + bool is_essential() const override { return true; } + + struct configuration + { + uint8_t configuration_version; + uint8_t general_profile_space; + bool general_tier_flag; + uint8_t general_profile_idc; + uint32_t general_profile_compatibility_flags; + + static const int NUM_CONSTRAINT_INDICATOR_FLAGS = 48; + std::bitset general_constraint_indicator_flags; + + uint8_t general_level_idc; + + uint16_t min_spatial_segmentation_idc; + uint8_t parallelism_type; + uint8_t chroma_format; + uint8_t bit_depth_luma; + uint8_t bit_depth_chroma; + uint16_t avg_frame_rate; + + uint8_t constant_frame_rate; + uint8_t num_temporal_layers; + uint8_t temporal_id_nested; + }; + + + std::string dump(Indent&) const override; + + bool get_headers(std::vector* dest) const; + + void set_configuration(const configuration& config) { m_configuration = config; } + + const configuration& get_configuration() const { return m_configuration; } + + void append_nal_data(const std::vector& nal); + + void append_nal_data(const uint8_t* data, size_t size); + + Error write(StreamWriter& writer) const override; + +protected: + Error parse(BitstreamRange& range, const heif_security_limits* limits) override; + +private: + struct NalArray + { + uint8_t m_array_completeness; + uint8_t m_NAL_unit_type; + + std::vector > m_nal_units; + }; + + configuration m_configuration; + uint8_t m_length_size = 4; // default: 4 bytes for NAL unit lengths + + std::vector m_nal_array; +}; + +class SEIMessage +{ +public: + virtual ~SEIMessage() = default; +}; + + +class SEIMessage_depth_representation_info : public SEIMessage, + public heif_depth_representation_info +{ +public: +}; + + +Error decode_hevc_aux_sei_messages(const std::vector& data, + std::vector>& msgs); + + +Error parse_sps_for_hvcC_configuration(const uint8_t* sps, size_t size, + Box_hvcC::configuration* inout_config, + int* width, int* height); + +#endif diff --git a/libheif/codecs/hevc_dec.cc b/libheif/codecs/hevc_dec.cc new file mode 100644 index 0000000000..3b08b49e90 --- /dev/null +++ b/libheif/codecs/hevc_dec.cc @@ -0,0 +1,65 @@ +/* + * HEIF codec. + * Copyright (c) 2024 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 "hevc_dec.h" +#include "hevc_boxes.h" +#include "error.h" +#include "context.h" + +#include + + +Result> Decoder_HEVC::read_bitstream_configuration_data() const +{ + std::vector data; + if (!m_hvcC->get_headers(&data)) { + return Error{heif_error_Invalid_input, + heif_suberror_No_item_data}; + } + + return data; +} + + +int Decoder_HEVC::get_luma_bits_per_pixel() const +{ + return m_hvcC->get_configuration().bit_depth_luma; +} + + +int Decoder_HEVC::get_chroma_bits_per_pixel() const +{ + return m_hvcC->get_configuration().bit_depth_chroma; +} + + +Error Decoder_HEVC::get_coded_image_colorspace(heif_colorspace* out_colorspace, heif_chroma* out_chroma) const +{ + *out_chroma = (heif_chroma) (m_hvcC->get_configuration().chroma_format); + + if (*out_chroma == heif_chroma_monochrome) { + *out_colorspace = heif_colorspace_monochrome; + } + else { + *out_colorspace = heif_colorspace_YCbCr; + } + + return Error::Ok; +} diff --git a/libheif/codecs/hevc_dec.h b/libheif/codecs/hevc_dec.h new file mode 100644 index 0000000000..ac4e67e0f0 --- /dev/null +++ b/libheif/codecs/hevc_dec.h @@ -0,0 +1,54 @@ +/* + * HEIF codec. + * Copyright (c) 2024 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 HEIF_HEVC_DEC_H +#define HEIF_HEVC_DEC_H + +#include "libheif/heif.h" +#include "box.h" +#include "error.h" + +#include +#include +#include + +class Box_hvcC; + + +class Decoder_HEVC : public Decoder +{ +public: + explicit Decoder_HEVC(const std::shared_ptr& hvcC) : m_hvcC(hvcC) {} + + heif_compression_format get_compression_format() const override { return heif_compression_HEVC; } + + int get_luma_bits_per_pixel() const override; + + int get_chroma_bits_per_pixel() const override; + + Error get_coded_image_colorspace(heif_colorspace*, heif_chroma*) const override; + + Result> read_bitstream_configuration_data() const override; + +private: + const std::shared_ptr m_hvcC; +}; + +#endif diff --git a/libheif/codecs/jpeg2000.cc b/libheif/codecs/jpeg2000_boxes.cc similarity index 94% rename from libheif/codecs/jpeg2000.cc rename to libheif/codecs/jpeg2000_boxes.cc index 03dee77df7..b41c28388a 100644 --- a/libheif/codecs/jpeg2000.cc +++ b/libheif/codecs/jpeg2000_boxes.cc @@ -18,17 +18,18 @@ * along with libheif. If not, see . */ -#include "jpeg2000.h" +#include "jpeg2000_boxes.h" +#include "libheif/api_structs.h" #include #include -#include +#include static const uint16_t JPEG2000_CAP_MARKER = 0xFF50; static const uint16_t JPEG2000_SIZ_MARKER = 0xFF51; static const uint16_t JPEG2000_SOC_MARKER = 0xFF4F; -Error Box_cdef::parse(BitstreamRange& range) +Error Box_cdef::parse(BitstreamRange& range, const heif_security_limits* limits) { int channel_count = range.read16(); @@ -111,7 +112,7 @@ void Box_cdef::set_channels(heif_colorspace colorspace) } } -Error Box_cmap::parse(BitstreamRange& range) +Error Box_cmap::parse(BitstreamRange& range, const heif_security_limits* limits) { while (!range.eof() && !range.error()) { Component component; @@ -156,7 +157,7 @@ Error Box_cmap::write(StreamWriter& writer) const } -Error Box_pclr::parse(BitstreamRange& range) +Error Box_pclr::parse(BitstreamRange& range, const heif_security_limits* limits) { uint16_t num_entries = range.read16(); uint8_t num_palette_columns = range.read8(); @@ -248,7 +249,7 @@ void Box_pclr::set_columns(uint8_t num_columns, uint8_t bit_depth) } } -Error Box_j2kL::parse(BitstreamRange& range) +Error Box_j2kL::parse(BitstreamRange& range, const heif_security_limits* limits) { int layer_count = range.read16(); @@ -295,9 +296,9 @@ Error Box_j2kL::write(StreamWriter& writer) const } -Error Box_j2kH::parse(BitstreamRange& range) +Error Box_j2kH::parse(BitstreamRange& range, const heif_security_limits* limits) { - return read_children(range); + return read_children(range, READ_CHILDREN_ALL, limits); } std::string Box_j2kH::dump(Indent& indent) const @@ -311,12 +312,11 @@ std::string Box_j2kH::dump(Indent& indent) const } -Error JPEG2000MainHeader::parseHeader(const HeifFile& file, const heif_item_id imageID) +Error JPEG2000MainHeader::parseHeader(const std::vector& compressedImageData) { - Error err = file.get_compressed_image_data(imageID, &headerData); - if (err) { - return err; - } + // TODO: it is very inefficient to store the whole image data when we only need the header + + headerData = compressedImageData; return doParse(); } diff --git a/libheif/codecs/jpeg2000.h b/libheif/codecs/jpeg2000_boxes.h similarity index 94% rename from libheif/codecs/jpeg2000.h rename to libheif/codecs/jpeg2000_boxes.h index e1b00486ab..c07e60467d 100644 --- a/libheif/codecs/jpeg2000.h +++ b/libheif/codecs/jpeg2000_boxes.h @@ -18,8 +18,8 @@ * along with libheif. If not, see . */ -#ifndef LIBHEIF_JPEG2000_H -#define LIBHEIF_JPEG2000_H +#ifndef LIBHEIF_JPEG2000_BOXES_H +#define LIBHEIF_JPEG2000_BOXES_H #include "box.h" #include "file.h" @@ -108,7 +108,7 @@ class Box_cdef : public Box { void set_channels(heif_colorspace colorspace); protected: - Error parse(BitstreamRange &range) override; + Error parse(BitstreamRange &range, const heif_security_limits* limits) override; private: std::vector m_channels; @@ -165,7 +165,7 @@ class Box_cmap : public Box void add_component(Component component) { m_components.push_back(component); } protected: - Error parse(BitstreamRange &range) override; + Error parse(BitstreamRange &range, const heif_security_limits* limits) override; private: std::vector m_components; @@ -245,7 +245,7 @@ class Box_pclr : public Box void set_columns(uint8_t num_columns, uint8_t bit_depth); protected: - Error parse(BitstreamRange &range) override; + Error parse(BitstreamRange &range, const heif_security_limits* limits) override; private: std::vector m_bitDepths; @@ -301,7 +301,7 @@ class Box_j2kL : public FullBox void add_layer(Layer layer) { m_layers.push_back(layer); } protected: - Error parse(BitstreamRange &range) override; + Error parse(BitstreamRange &range, const heif_security_limits* limits) override; private: std::vector m_layers; @@ -312,30 +312,30 @@ class Box_j2kH : public Box public: Box_j2kH() { set_short_type(fourcc("j2kH")); } + bool is_essential() const override { return true; } + std::string dump(Indent &) const override; // Default write behaviour for a container is to write children protected: - Error parse(BitstreamRange &range) override; + Error parse(BitstreamRange &range, const heif_security_limits* limits) override; }; class Jpeg2000ImageCodec { public: -// static Error decode_jpeg2000_image(const std::shared_ptr& heif_file, +// static Error decode_jpeg2000_image(const HeifContext* context, // heif_item_id ID, // std::shared_ptr& img, -// uint32_t maximum_image_width_limit, -// uint32_t maximum_image_height_limit, // const std::vector& uncompressed_data); static Error encode_jpeg2000_image(const std::shared_ptr& heif_file, const std::shared_ptr& src_image, void* encoder_struct, const struct heif_encoding_options& options, - std::shared_ptr& out_image); + std::shared_ptr& out_image); }; struct JPEG2000_SIZ_segment @@ -440,7 +440,7 @@ struct JPEG2000MainHeader public: JPEG2000MainHeader() = default; - Error parseHeader(const HeifFile& file, const heif_item_id imageID); + Error parseHeader(const std::vector& compressedImageData); // Use parseHeader instead - these are mainly for unit testing Error doParse(); @@ -515,4 +515,4 @@ struct JPEG2000MainHeader size_t cursor; }; -#endif // LIBHEIF_JPEG2000_H +#endif // LIBHEIF_JPEG2000_BOXES_H diff --git a/libheif/codecs/jpeg2000_dec.cc b/libheif/codecs/jpeg2000_dec.cc new file mode 100644 index 0000000000..a14a661dfe --- /dev/null +++ b/libheif/codecs/jpeg2000_dec.cc @@ -0,0 +1,84 @@ +/* + * HEIF codec. + * Copyright (c) 2024 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 "jpeg2000_dec.h" +#include "jpeg2000_boxes.h" +#include "error.h" +#include "context.h" + +#include + + +Result> Decoder_JPEG2000::read_bitstream_configuration_data() const +{ + return std::vector{}; +} + + +int Decoder_JPEG2000::get_luma_bits_per_pixel() const +{ + Result> imageDataResult = get_compressed_data(); + if (imageDataResult.error) { + return -1; + } + + JPEG2000MainHeader header; + Error err = header.parseHeader(*imageDataResult); + if (err) { + return -1; + } + return header.get_precision(0); +} + + +int Decoder_JPEG2000::get_chroma_bits_per_pixel() const +{ + Result> imageDataResult = get_compressed_data(); + if (imageDataResult.error) { + return -1; + } + + JPEG2000MainHeader header; + Error err = header.parseHeader(*imageDataResult); + if (err) { + return -1; + } + return header.get_precision(1); +} + + +Error Decoder_JPEG2000::get_coded_image_colorspace(heif_colorspace* out_colorspace, heif_chroma* out_chroma) const +{ +#if 0 + *out_chroma = (heif_chroma) (m_hvcC->get_configuration().chroma_format); + + if (*out_chroma == heif_chroma_monochrome) { + *out_colorspace = heif_colorspace_monochrome; + } + else { + *out_colorspace = heif_colorspace_YCbCr; + } +#endif + + *out_colorspace = heif_colorspace_YCbCr; + *out_chroma = heif_chroma_444; + + return Error::Ok; +} diff --git a/libheif/codecs/jpeg2000_dec.h b/libheif/codecs/jpeg2000_dec.h new file mode 100644 index 0000000000..083c78f955 --- /dev/null +++ b/libheif/codecs/jpeg2000_dec.h @@ -0,0 +1,59 @@ +/* + * HEIF codec. + * Copyright (c) 2024 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 HEIF_JPEG2000_DEC_H +#define HEIF_JPEG2000_DEC_H + +#include "libheif/heif.h" +#include "box.h" +#include "error.h" +#include "file.h" + +#include +#include +#include +#include +#include "image-items/jpeg2000.h" +#include + + +class Decoder_JPEG2000 : public Decoder +{ +public: + Decoder_JPEG2000(const std::shared_ptr& j2kH) : m_j2kH(j2kH) {} + + heif_compression_format get_compression_format() const override { return heif_compression_JPEG2000; } + + void set_data_extent(DataExtent extent) { m_data_extent = std::move(extent); } + + int get_luma_bits_per_pixel() const override; + + int get_chroma_bits_per_pixel() const override; + + Error get_coded_image_colorspace(heif_colorspace*, heif_chroma*) const override; + + Result> read_bitstream_configuration_data() const override; + +private: + const std::shared_ptr m_j2kH; + DataExtent m_data_extent; +}; + +#endif diff --git a/libheif/codecs/jpeg.cc b/libheif/codecs/jpeg_boxes.cc similarity index 76% rename from libheif/codecs/jpeg.cc rename to libheif/codecs/jpeg_boxes.cc index 22347cdd26..bcb145850a 100644 --- a/libheif/codecs/jpeg.cc +++ b/libheif/codecs/jpeg_boxes.cc @@ -18,9 +18,25 @@ * along with libheif. If not, see . */ -#include "jpeg.h" +#include "jpeg_boxes.h" #include #include "security_limits.h" +#include +#include "libheif/heif_experimental.h" + + +// returns 0 if the marker_type was not found +size_t find_jpeg_marker_start(const std::vector& data, uint8_t marker_type) +{ + for (size_t i = 0; i < data.size() - 1; i++) { + if (data[i] == 0xFF && data[i + 1] == marker_type) { + return i; + } + } + + return 0; +} + std::string Box_jpgC::dump(Indent& indent) const { @@ -45,14 +61,14 @@ Error Box_jpgC::write(StreamWriter& writer) const } -Error Box_jpgC::parse(BitstreamRange& range) +Error Box_jpgC::parse(BitstreamRange& range, const heif_security_limits* limits) { if (!has_fixed_box_size()) { return Error{heif_error_Unsupported_feature, heif_suberror_Unspecified, "jpgC with unspecified size are not supported"}; } size_t nBytes = range.get_remaining_bytes(); - if (nBytes > MAX_MEMORY_BLOCK_SIZE) { + if (nBytes > limits->max_memory_block_size) { return Error{heif_error_Invalid_input, heif_suberror_Unspecified, "jpgC block exceeds maximum size"}; } @@ -60,3 +76,4 @@ Error Box_jpgC::parse(BitstreamRange& range) range.read(m_data.data(), nBytes); return range.get_error(); } + diff --git a/libheif/codecs/jpeg.h b/libheif/codecs/jpeg_boxes.h similarity index 79% rename from libheif/codecs/jpeg.h rename to libheif/codecs/jpeg_boxes.h index 3958bfaccf..8a9e56a0bc 100644 --- a/libheif/codecs/jpeg.h +++ b/libheif/codecs/jpeg_boxes.h @@ -18,12 +18,15 @@ * along with libheif. If not, see . */ -#ifndef LIBHEIF_JPEG_H -#define LIBHEIF_JPEG_H +#ifndef LIBHEIF_JPEG_BOXES_H +#define LIBHEIF_JPEG_BOXES_H #include "box.h" #include #include +#include "image-items/image_item.h" +#include + class Box_jpgC : public Box { @@ -33,7 +36,7 @@ class Box_jpgC : public Box set_short_type(fourcc("jpgC")); } - const std::vector& get_data() { return m_data; } + const std::vector& get_data() const { return m_data; } void set_data(const std::vector& data) { m_data = data; } @@ -42,11 +45,10 @@ class Box_jpgC : public Box Error write(StreamWriter& writer) const override; protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits* limits) override; private: std::vector m_data; }; - -#endif // LIBHEIF_JPEG_H +#endif // LIBHEIF_JPEG_BOXES_H diff --git a/libheif/codecs/jpeg_dec.cc b/libheif/codecs/jpeg_dec.cc new file mode 100644 index 0000000000..fdc6e0472e --- /dev/null +++ b/libheif/codecs/jpeg_dec.cc @@ -0,0 +1,160 @@ +/* + * HEIF codec. + * Copyright (c) 2024 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 "jpeg_dec.h" +#include "jpeg_boxes.h" +#include "error.h" +#include "context.h" + +#include +#include + + +Result> Decoder_JPEG::read_bitstream_configuration_data() const +{ + if (m_jpgC) { + return m_jpgC->get_data(); + } + else { + return std::vector{}; + } +} + + +// This checks whether a start code FFCx with nibble 'x' is a SOF marker. +// E.g. FFC0-FFC3 are, while FFC4 is not. +static bool isSOF[16] = {true, true, true, true, false, true, true, true, + false, true, true, true, false, true, true, true}; + +Error Decoder_JPEG::parse_SOF() +{ + if (m_config) { + return Error::Ok; + } + + // image data, usually from 'mdat' + + auto dataResult = get_compressed_data(); + if (dataResult.error) { + return dataResult.error; + } + + const std::vector& data = dataResult.value; + + const Error error_invalidSOF{heif_error_Invalid_input, + heif_suberror_Unspecified, + "Invalid JPEG SOF header"}; + + for (size_t i = 0; i + 1 < data.size(); i++) { + if (data[i] == 0xFF && (data[i + 1] & 0xF0) == 0xC0 && isSOF[data[i + 1] & 0x0F]) { + + if (i + 9 >= data.size()) { + return error_invalidSOF; + } + + ConfigInfo info; + info.sample_precision = data[i + 4]; + info.nComponents = data[i + 9]; + + if (i + 11 + 3 * info.nComponents >= data.size()) { + return error_invalidSOF; + } + + for (int c = 0; c < std::min(info.nComponents, uint8_t(3)); c++) { + int ss = data[i + 11 + 3 * c]; + info.h_sampling[c] = (ss >> 4) & 0xF; + info.v_sampling[c] = ss & 0xF; + } + + if (info.nComponents == 1) { + info.chroma = heif_chroma_monochrome; + } + else if (info.nComponents != 3) { + return error_invalidSOF; + } + else { + if (info.h_sampling[1] != info.h_sampling[2] || + info.v_sampling[1] != info.v_sampling[2]) { + return error_invalidSOF; + } + + if (info.h_sampling[0] == 2 && info.v_sampling[0] == 2 && + info.h_sampling[1] == 1 && info.v_sampling[1] == 1) { + info.chroma = heif_chroma_420; + } + else if (info.h_sampling[0] == 2 && info.v_sampling[0] == 1 && + info.h_sampling[1] == 1 && info.v_sampling[1] == 1) { + info.chroma = heif_chroma_422; + } + else if (info.h_sampling[0] == 1 && info.v_sampling[0] == 1 && + info.h_sampling[1] == 1 && info.v_sampling[1] == 1) { + info.chroma = heif_chroma_444; + } + else { + return error_invalidSOF; + } + } + + m_config = info; + + return Error::Ok; + } + } + + return error_invalidSOF; +} + + +int Decoder_JPEG::get_luma_bits_per_pixel() const +{ + Error err = const_cast(this)->parse_SOF(); + if (err) { + return -1; + } + else { + return m_config->sample_precision; + } +} + + +int Decoder_JPEG::get_chroma_bits_per_pixel() const +{ + return get_luma_bits_per_pixel(); +} + + +Error Decoder_JPEG::get_coded_image_colorspace(heif_colorspace* out_colorspace, heif_chroma* out_chroma) const +{ + Error err = const_cast(this)->parse_SOF(); + if (err) { + return err; + } + + *out_chroma = m_config->chroma; + + if (*out_chroma == heif_chroma_monochrome) { + *out_colorspace = heif_colorspace_monochrome; + } + else { + *out_colorspace = heif_colorspace_YCbCr; + } + + return Error::Ok; +} diff --git a/libheif/codecs/jpeg_dec.h b/libheif/codecs/jpeg_dec.h new file mode 100644 index 0000000000..4de18eb58e --- /dev/null +++ b/libheif/codecs/jpeg_dec.h @@ -0,0 +1,68 @@ +/* + * HEIF codec. + * Copyright (c) 2024 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 HEIF_JPEG_DEC_H +#define HEIF_JPEG_DEC_H + +#include "libheif/heif.h" +#include "box.h" +#include "error.h" +#include "codecs/decoder.h" + +#include +#include +#include + +class Box_jpgC; + + +class Decoder_JPEG : public Decoder +{ +public: + explicit Decoder_JPEG(const std::shared_ptr& jpgC) : m_jpgC(jpgC) {} + + heif_compression_format get_compression_format() const override { return heif_compression_JPEG; } + + int get_luma_bits_per_pixel() const override; + + int get_chroma_bits_per_pixel() const override; + + Error get_coded_image_colorspace(heif_colorspace*, heif_chroma*) const override; + + Result> read_bitstream_configuration_data() const override; + +private: + const std::shared_ptr m_jpgC; // Optional jpgC box. May be NULL. + + struct ConfigInfo { + uint8_t sample_precision = 0; + heif_chroma chroma = heif_chroma_undefined; + + uint8_t nComponents = 0; + uint8_t h_sampling[3]{}; + uint8_t v_sampling[3]{}; + }; + + std::optional m_config; + + Error parse_SOF(); +}; + +#endif diff --git a/libheif/codecs/uncompressed/decoder_abstract.cc b/libheif/codecs/uncompressed/decoder_abstract.cc new file mode 100644 index 0000000000..7b66edbcd4 --- /dev/null +++ b/libheif/codecs/uncompressed/decoder_abstract.cc @@ -0,0 +1,270 @@ +/* + * HEIF codec. + * Copyright (c) 2023 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 +#include +#include +#include + +#include "common_utils.h" +#include "context.h" +#include "compression.h" +#include "error.h" +#include "libheif/heif.h" +#include "unc_types.h" +#include "unc_boxes.h" +#include "unc_codec.h" +#include "decoder_abstract.h" + + +AbstractDecoder::AbstractDecoder(uint32_t width, uint32_t height, const std::shared_ptr cmpd, const std::shared_ptr uncC) : + m_width(width), + m_height(height), + m_cmpd(std::move(cmpd)), + m_uncC(std::move(uncC)) +{ + m_tile_height = m_height / m_uncC->get_number_of_tile_rows(); + m_tile_width = m_width / m_uncC->get_number_of_tile_columns(); + + assert(m_tile_width > 0); + assert(m_tile_height > 0); +} + +void AbstractDecoder::buildChannelList(std::shared_ptr& img) +{ + for (Box_uncC::Component component : m_uncC->get_components()) { + ChannelListEntry entry = buildChannelListEntry(component, img); + channelList.push_back(entry); + } +} + +void AbstractDecoder::processComponentSample(UncompressedBitReader& srcBits, const ChannelListEntry& entry, uint64_t dst_row_offset, uint32_t tile_column, uint32_t tile_x) +{ + uint64_t dst_col_number = tile_column * entry.tile_width + tile_x; + uint64_t dst_column_offset = dst_col_number * entry.bytes_per_component_sample; + int val = srcBits.get_bits(entry.bits_per_component_sample); + memcpy(entry.dst_plane + dst_row_offset + dst_column_offset, &val, entry.bytes_per_component_sample); +} + +// Handles the case where a row consists of a single component type +// Not valid for Pixel interleave +// Not valid for the Cb/Cr channels in Mixed Interleave +// Not valid for multi-Y pixel interleave +void AbstractDecoder::processComponentRow(ChannelListEntry& entry, UncompressedBitReader& srcBits, uint64_t dst_row_offset, uint32_t tile_column) +{ + for (uint32_t tile_x = 0; tile_x < entry.tile_width; tile_x++) { + if (entry.component_alignment != 0) { + srcBits.skip_to_byte_boundary(); + int numPadBits = (entry.component_alignment * 8) - entry.bits_per_component_sample; + srcBits.skip_bits(numPadBits); + } + processComponentSample(srcBits, entry, dst_row_offset, tile_column, tile_x); + } + srcBits.skip_to_byte_boundary(); +} + +void AbstractDecoder::processComponentTileSample(UncompressedBitReader& srcBits, const ChannelListEntry& entry, uint64_t dst_offset, uint32_t tile_x) +{ + uint64_t dst_sample_offset = tile_x * entry.bytes_per_component_sample; + int val = srcBits.get_bits(entry.bits_per_component_sample); + memcpy(entry.dst_plane + dst_offset + dst_sample_offset, &val, entry.bytes_per_component_sample); +} + +// Handles the case where a row consists of a single component type +// Not valid for Pixel interleave +// Not valid for the Cb/Cr channels in Mixed Interleave +// Not valid for multi-Y pixel interleave +void AbstractDecoder::processComponentTileRow(ChannelListEntry& entry, UncompressedBitReader& srcBits, uint64_t dst_offset) +{ + for (uint32_t tile_x = 0; tile_x < entry.tile_width; tile_x++) { + if (entry.component_alignment != 0) { + srcBits.skip_to_byte_boundary(); + int numPadBits = (entry.component_alignment * 8) - entry.bits_per_component_sample; + srcBits.skip_bits(numPadBits); + } + processComponentTileSample(srcBits, entry, dst_offset, tile_x); + } + srcBits.skip_to_byte_boundary(); +} + + +AbstractDecoder::ChannelListEntry AbstractDecoder::buildChannelListEntry(Box_uncC::Component component, + std::shared_ptr& img) +{ + ChannelListEntry entry; + entry.use_channel = map_uncompressed_component_to_channel(m_cmpd, m_uncC, component, &(entry.channel)); + entry.dst_plane = img->get_plane(entry.channel, &(entry.dst_plane_stride)); + entry.tile_width = m_tile_width; + entry.tile_height = m_tile_height; + if ((entry.channel == heif_channel_Cb) || (entry.channel == heif_channel_Cr)) { + if (m_uncC->get_sampling_type() == sampling_mode_422) { + entry.tile_width /= 2; + } + else if (m_uncC->get_sampling_type() == sampling_mode_420) { + entry.tile_width /= 2; + entry.tile_height /= 2; + } + if (entry.channel == heif_channel_Cb) { + entry.other_chroma_dst_plane = img->get_plane(heif_channel_Cr, &(entry.other_chroma_dst_plane_stride)); + } + else if (entry.channel == heif_channel_Cr) { + entry.other_chroma_dst_plane = img->get_plane(heif_channel_Cb, &(entry.other_chroma_dst_plane_stride)); + } + } + entry.bits_per_component_sample = component.component_bit_depth; + entry.component_alignment = component.component_align_size; + entry.bytes_per_component_sample = (component.component_bit_depth + 7) / 8; + entry.bytes_per_tile_row_src = entry.tile_width * entry.bytes_per_component_sample; + return entry; +} + + +// generic compression and uncompressed, per 23001-17 +const Error AbstractDecoder::get_compressed_image_data_uncompressed(const HeifContext* context, heif_item_id ID, + std::vector* data, + uint64_t range_start_offset, uint64_t range_size, + uint32_t tile_idx, + const Box_iloc::Item* item) const +{ + // --- get codec configuration + + std::shared_ptr cmpC_box = context->get_heif_file()->get_property(ID); + std::shared_ptr icef_box = context->get_heif_file()->get_property(ID); + + if (!cmpC_box) { + // assume no generic compression + return context->get_heif_file()->append_data_from_iloc(ID, *data, range_start_offset, range_size); + } + + if (icef_box && cmpC_box->get_compressed_unit_type() == heif_cmpC_compressed_unit_type_image_tile) { + const auto& units = icef_box->get_units(); + if (tile_idx >= units.size()) { + return {heif_error_Invalid_input, + heif_suberror_Unspecified, + "no icef-box entry for tile index"}; + } + + const auto unit = units[tile_idx]; + + // get all data and decode all + std::vector compressed_bytes; + Error err = context->get_heif_file()->append_data_from_iloc(ID, compressed_bytes, unit.unit_offset, unit.unit_size); + if (err) { + return err; + } + + // decompress only the unit + err = do_decompress_data(cmpC_box, compressed_bytes, data); + if (err) { + return err; + } + } + else if (icef_box) { + // get all data and decode all + std::vector compressed_bytes; + Error err = context->get_heif_file()->append_data_from_iloc(ID, compressed_bytes); // image_id, src_data, tile_start_offset, total_tile_size); + if (err) { + return err; + } + + for (Box_icef::CompressedUnitInfo unit_info : icef_box->get_units()) { + auto unit_start = compressed_bytes.begin() + unit_info.unit_offset; + auto unit_end = unit_start + unit_info.unit_size; + std::vector compressed_unit_data = std::vector(unit_start, unit_end); + std::vector uncompressed_unit_data; + err = do_decompress_data(cmpC_box, compressed_unit_data, &uncompressed_unit_data); + if (err) { + return err; + } + data->insert(data->end(), uncompressed_unit_data.data(), uncompressed_unit_data.data() + uncompressed_unit_data.size()); + } + + // cut out the range that we actually need + memcpy(data->data(), data->data() + range_start_offset, range_size); + data->resize(range_size); + } + else { + // get all data and decode all + std::vector compressed_bytes; + Error err = context->get_heif_file()->append_data_from_iloc(ID, compressed_bytes); // image_id, src_data, tile_start_offset, total_tile_size); + if (err) { + return err; + } + + // Decode as a single blob + err = do_decompress_data(cmpC_box, compressed_bytes, data); + if (err) { + return err; + } + + // cut out the range that we actually need + memcpy(data->data(), data->data() + range_start_offset, range_size); + data->resize(range_size); + } + + return Error::Ok; +} + +const Error AbstractDecoder::do_decompress_data(std::shared_ptr& cmpC_box, + std::vector compressed_data, + std::vector* data) const +{ + if (cmpC_box->get_compression_type() == fourcc("brot")) { +#if HAVE_BROTLI + return decompress_brotli(compressed_data, data); +#else + std::stringstream sstr; + sstr << "cannot decode unci item with brotli compression - not enabled" << std::endl; + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_generic_compression_method, + sstr.str()); +#endif + } + else if (cmpC_box->get_compression_type() == fourcc("zlib")) { +#if HAVE_ZLIB + return decompress_zlib(compressed_data, data); +#else + std::stringstream sstr; + sstr << "cannot decode unci item with zlib compression - not enabled" << std::endl; + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_generic_compression_method, + sstr.str()); +#endif + } + else if (cmpC_box->get_compression_type() == fourcc("defl")) { +#if HAVE_ZLIB + return decompress_deflate(compressed_data, data); +#else + std::stringstream sstr; + sstr << "cannot decode unci item with deflate compression - not enabled" << std::endl; + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_generic_compression_method, + sstr.str()); +#endif + } + else { + std::stringstream sstr; + sstr << "cannot decode unci item with unsupported compression type: " << cmpC_box->get_compression_type() << std::endl; + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_generic_compression_method, + sstr.str()); + } +} diff --git a/libheif/codecs/uncompressed/decoder_abstract.h b/libheif/codecs/uncompressed/decoder_abstract.h new file mode 100644 index 0000000000..60d5acb2d9 --- /dev/null +++ b/libheif/codecs/uncompressed/decoder_abstract.h @@ -0,0 +1,208 @@ +/* + * HEIF codec. + * Copyright (c) 2023 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 UNCI_DECODER_ABSTRACT_H +#define UNCI_DECODER_ABSTRACT_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common_utils.h" +#include "context.h" +#include "compression.h" +#include "error.h" +#include "libheif/heif.h" +#include "unc_types.h" +#include "unc_boxes.h" +#include "unc_codec.h" + + +class UncompressedBitReader : public BitReader +{ +public: + UncompressedBitReader(const std::vector& data) : BitReader(data.data(), (int) data.size()) {} + + void markPixelStart() + { + m_pixelStartOffset = get_current_byte_index(); + } + + void markRowStart() + { + m_rowStartOffset = get_current_byte_index(); + } + + void markTileStart() + { + m_tileStartOffset = get_current_byte_index(); + } + + inline void handlePixelAlignment(uint32_t pixel_size) + { + if (pixel_size != 0) { + uint32_t bytes_in_pixel = get_current_byte_index() - m_pixelStartOffset; + uint32_t padding = pixel_size - bytes_in_pixel; + skip_bytes(padding); + } + } + + void handleRowAlignment(uint32_t alignment) + { + skip_to_byte_boundary(); + if (alignment != 0) { + uint32_t bytes_in_row = get_current_byte_index() - m_rowStartOffset; + uint32_t residual = bytes_in_row % alignment; + if (residual != 0) { + uint32_t padding = alignment - residual; + skip_bytes(padding); + } + } + } + + void handleTileAlignment(uint32_t alignment) + { + if (alignment != 0) { + uint32_t bytes_in_tile = get_current_byte_index() - m_tileStartOffset; + uint32_t residual = bytes_in_tile % alignment; + if (residual != 0) { + uint32_t tile_padding = alignment - residual; + skip_bytes(tile_padding); + } + } + } + +private: + int m_pixelStartOffset; + int m_rowStartOffset; + int m_tileStartOffset; +}; + + +template void skip_to_alignment(T& position, uint32_t alignment) +{ + if (alignment == 0) { + return; + } + + T residual = position % alignment; + if (residual == 0) { + return; + } + + position += alignment - residual; +} + + +class AbstractDecoder +{ +public: + virtual ~AbstractDecoder() = default; + + virtual Error decode_tile(const HeifContext* context, + heif_item_id item_id, + std::shared_ptr& img, + uint32_t out_x0, uint32_t out_y0, + uint32_t image_width, uint32_t image_height, + uint32_t tile_x, uint32_t tile_y) = 0; + + void buildChannelList(std::shared_ptr& img); + +protected: + AbstractDecoder(uint32_t width, uint32_t height, + const std::shared_ptr cmpd, + const std::shared_ptr uncC); + + const uint32_t m_width; + const uint32_t m_height; + const std::shared_ptr m_cmpd; + const std::shared_ptr m_uncC; + // TODO: see if we can make this const + uint32_t m_tile_height; + uint32_t m_tile_width; + + class ChannelListEntry + { + public: + uint32_t get_bytes_per_tile() const + { + return bytes_per_tile_row_src * tile_height; + } + + inline uint64_t getDestinationRowOffset(uint32_t tile_row, uint32_t tile_y) const + { + uint64_t dst_row_number = tile_row * tile_height + tile_y; + return dst_row_number * dst_plane_stride; + } + + heif_channel channel = heif_channel_Y; + uint8_t* dst_plane; + uint8_t* other_chroma_dst_plane; + uint32_t dst_plane_stride; + uint32_t other_chroma_dst_plane_stride; + uint32_t tile_width; + uint32_t tile_height; + uint32_t bytes_per_component_sample; + uint16_t bits_per_component_sample; + uint8_t component_alignment; + uint32_t bytes_per_tile_row_src; + bool use_channel; + }; + + std::vector channelList; + + void processComponentSample(UncompressedBitReader& srcBits, const ChannelListEntry& entry, uint64_t dst_row_offset, uint32_t tile_column, uint32_t tile_x); + + // Handles the case where a row consists of a single component type + // Not valid for Pixel interleave + // Not valid for the Cb/Cr channels in Mixed Interleave + // Not valid for multi-Y pixel interleave + void processComponentRow(ChannelListEntry& entry, UncompressedBitReader& srcBits, uint64_t dst_row_offset, uint32_t tile_column); + + void processComponentTileSample(UncompressedBitReader& srcBits, const ChannelListEntry& entry, uint64_t dst_offset, uint32_t tile_x); + + // Handles the case where a row consists of a single component type + // Not valid for Pixel interleave + // Not valid for the Cb/Cr channels in Mixed Interleave + // Not valid for multi-Y pixel interleave + void processComponentTileRow(ChannelListEntry& entry, UncompressedBitReader& srcBits, uint64_t dst_offset); + + // generic compression and uncompressed, per 23001-17 + const Error get_compressed_image_data_uncompressed(const HeifContext* context, heif_item_id ID, + std::vector* data, + uint64_t range_start_offset, uint64_t range_size, + uint32_t tile_idx, + const Box_iloc::Item* item) const; + + const Error do_decompress_data(std::shared_ptr& cmpC_box, + std::vector compressed_data, + std::vector* data) const; + +private: + ChannelListEntry buildChannelListEntry(Box_uncC::Component component, std::shared_ptr& img); +}; + +#endif diff --git a/libheif/codecs/uncompressed/decoder_component_interleave.cc b/libheif/codecs/uncompressed/decoder_component_interleave.cc new file mode 100644 index 0000000000..5c20e284c9 --- /dev/null +++ b/libheif/codecs/uncompressed/decoder_component_interleave.cc @@ -0,0 +1,96 @@ +/* + * HEIF codec. + * Copyright (c) 2023 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 "decoder_component_interleave.h" +#include "context.h" +#include "error.h" + +#include +#include + + +Error ComponentInterleaveDecoder::decode_tile(const HeifContext* context, + heif_item_id image_id, + std::shared_ptr& img, + uint32_t out_x0, uint32_t out_y0, + uint32_t image_width, uint32_t image_height, + uint32_t tile_x, uint32_t tile_y) +{ + if (m_tile_width == 0) { + return {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "Internal error: ComponentInterleaveDecoder tile_width=0"}; + } + + // --- compute which file range we need to read for the tile + + uint64_t total_tile_size = 0; + + for (ChannelListEntry& entry : channelList) { + uint32_t bits_per_component = entry.bits_per_component_sample; + if (entry.component_alignment > 0) { + uint32_t bytes_per_component = (bits_per_component + 7) / 8; + skip_to_alignment(bytes_per_component, entry.component_alignment); + bits_per_component = bytes_per_component * 8; + } + + uint32_t bytes_per_tile_row = (bits_per_component * entry.tile_width + 7) / 8; + skip_to_alignment(bytes_per_tile_row, m_uncC->get_row_align_size()); + uint64_t bytes_per_tile = bytes_per_tile_row * entry.tile_height; + total_tile_size += bytes_per_tile; + } + + if (m_uncC->get_tile_align_size() != 0) { + skip_to_alignment(total_tile_size, m_uncC->get_tile_align_size()); + } + + assert(m_tile_width > 0); + uint32_t tileIdx = tile_x + tile_y * (image_width / m_tile_width); + uint64_t tile_start_offset = total_tile_size * tileIdx; + + + // --- read required file range + + std::vector src_data; + //Error err = context->get_heif_file()->append_data_from_iloc(image_id, src_data, tile_start_offset, total_tile_size); + Error err = get_compressed_image_data_uncompressed(context, image_id, &src_data, tile_start_offset, total_tile_size, tileIdx, nullptr); + if (err) { + return err; + } + + UncompressedBitReader srcBits(src_data); + + + // --- decode tile + + for (ChannelListEntry& entry : channelList) { + for (uint32_t y = 0; y < entry.tile_height; y++) { + srcBits.markRowStart(); + if (entry.use_channel) { + uint64_t dst_row_offset = (out_y0 + y) * entry.dst_plane_stride; + processComponentTileRow(entry, srcBits, dst_row_offset + out_x0 * entry.bytes_per_component_sample); + } + else { + srcBits.skip_bytes(entry.bytes_per_tile_row_src); + } + srcBits.handleRowAlignment(m_uncC->get_row_align_size()); + } + } + + return Error::Ok; +} diff --git a/libheif/codecs/uncompressed/decoder_component_interleave.h b/libheif/codecs/uncompressed/decoder_component_interleave.h new file mode 100644 index 0000000000..f25f40491f --- /dev/null +++ b/libheif/codecs/uncompressed/decoder_component_interleave.h @@ -0,0 +1,45 @@ +/* + * HEIF codec. + * Copyright (c) 2023 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 UNCI_DECODER_COMPONENT_INTERLEAVE_H +#define UNCI_DECODER_COMPONENT_INTERLEAVE_H + +#include "decoder_abstract.h" +#include +#include + + +class ComponentInterleaveDecoder : public AbstractDecoder +{ +public: + ComponentInterleaveDecoder(uint32_t width, uint32_t height, + std::shared_ptr cmpd, + std::shared_ptr uncC) : + AbstractDecoder(width, height, std::move(cmpd), std::move(uncC)) {} + + Error decode_tile(const HeifContext* context, + heif_item_id image_id, + std::shared_ptr& img, + uint32_t out_x0, uint32_t out_y0, + uint32_t image_width, uint32_t image_height, + uint32_t tile_x, uint32_t tile_y) override; +}; + +#endif // UNCI_DECODER_COMPONENT_INTERLEAVE_H diff --git a/libheif/codecs/uncompressed/decoder_mixed_interleave.cc b/libheif/codecs/uncompressed/decoder_mixed_interleave.cc new file mode 100644 index 0000000000..4187315b67 --- /dev/null +++ b/libheif/codecs/uncompressed/decoder_mixed_interleave.cc @@ -0,0 +1,130 @@ +/* + * HEIF codec. + * Copyright (c) 2023 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 "decoder_mixed_interleave.h" +#include "context.h" +#include "error.h" + +#include +#include +#include + + +Error MixedInterleaveDecoder::decode_tile(const HeifContext* context, + heif_item_id image_id, + std::shared_ptr& img, + uint32_t out_x0, uint32_t out_y0, + uint32_t image_width, uint32_t image_height, + uint32_t tile_x, uint32_t tile_y) +{ + if (m_tile_width == 0) { + return {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "Internal error: MixedInterleaveDecoder tile_width=0"}; + } + + // --- compute which file range we need to read for the tile + + uint64_t tile_size = 0; + + for (ChannelListEntry& entry : channelList) { + if (entry.channel == heif_channel_Cb || entry.channel == heif_channel_Cr) { + uint32_t bits_per_row = entry.bits_per_component_sample * entry.tile_width; + bits_per_row = (bits_per_row + 7) & ~7U; // align to byte boundary + + tile_size += bits_per_row / 8 * entry.tile_height; + } + else { + uint32_t bits_per_component = entry.bits_per_component_sample; + if (entry.component_alignment > 0) { + uint32_t bytes_per_component = (bits_per_component + 7) / 8; + skip_to_alignment(bytes_per_component, entry.component_alignment); + bits_per_component = bytes_per_component * 8; + } + + uint32_t bits_per_row = bits_per_component * entry.tile_width; + bits_per_row = (bits_per_row + 7) & ~7U; // align to byte boundary + + tile_size += bits_per_row / 8 * entry.tile_height; + } + } + + + if (m_uncC->get_tile_align_size() != 0) { + skip_to_alignment(tile_size, m_uncC->get_tile_align_size()); + } + + assert(m_tile_width > 0); + uint32_t tileIdx = tile_x + tile_y * (image_width / m_tile_width); + uint64_t tile_start_offset = tile_size * tileIdx; + + + // --- read required file range + + std::vector src_data; + Error err = get_compressed_image_data_uncompressed(context, image_id, &src_data, tile_start_offset, tile_size, tileIdx, nullptr); + //Error err = context->get_heif_file()->append_data_from_iloc(image_id, src_data, tile_start_offset, tile_size); + if (err) { + return err; + } + + UncompressedBitReader srcBits(src_data); + + processTile(srcBits, tile_y, tile_x, out_x0, out_y0); + + return Error::Ok; +} + + +void MixedInterleaveDecoder::processTile(UncompressedBitReader& srcBits, uint32_t tile_row, uint32_t tile_column, uint32_t out_x0, uint32_t out_y0) +{ + bool haveProcessedChromaForThisTile = false; + for (ChannelListEntry& entry : channelList) { + if (entry.use_channel) { + if ((entry.channel == heif_channel_Cb) || (entry.channel == heif_channel_Cr)) { + if (!haveProcessedChromaForThisTile) { + for (uint32_t tile_y = 0; tile_y < entry.tile_height; tile_y++) { + // TODO: row padding + uint64_t dst_row_number = tile_y + out_y0; + uint64_t dst_row_offset = dst_row_number * entry.dst_plane_stride; + for (uint32_t tile_x = 0; tile_x < entry.tile_width; tile_x++) { + uint64_t dst_column_number = out_x0 + tile_x; + uint64_t dst_column_offset = dst_column_number * entry.bytes_per_component_sample; + int val = srcBits.get_bits(entry.bytes_per_component_sample * 8); + memcpy(entry.dst_plane + dst_row_offset + dst_column_offset, &val, entry.bytes_per_component_sample); + val = srcBits.get_bits(entry.bytes_per_component_sample * 8); + memcpy(entry.other_chroma_dst_plane + dst_row_offset + dst_column_offset, &val, entry.bytes_per_component_sample); + } + haveProcessedChromaForThisTile = true; + } + } + } + else { + for (uint32_t tile_y = 0; tile_y < entry.tile_height; tile_y++) { + uint64_t dst_row_offset = entry.getDestinationRowOffset(tile_row, tile_y); + processComponentRow(entry, srcBits, dst_row_offset, tile_column); + } + } + } + else { + // skip over the data we are not using + srcBits.skip_bytes(entry.get_bytes_per_tile()); + continue; + } + } +} diff --git a/libheif/codecs/uncompressed/decoder_mixed_interleave.h b/libheif/codecs/uncompressed/decoder_mixed_interleave.h new file mode 100644 index 0000000000..c0f26abc64 --- /dev/null +++ b/libheif/codecs/uncompressed/decoder_mixed_interleave.h @@ -0,0 +1,46 @@ +/* + * HEIF codec. + * Copyright (c) 2023 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 UNCI_DECODER_MIXED_INTERLEAVE_H +#define UNCI_DECODER_MIXED_INTERLEAVE_H + +#include "decoder_abstract.h" +#include +#include + + +class MixedInterleaveDecoder : public AbstractDecoder +{ +public: + MixedInterleaveDecoder(uint32_t width, uint32_t height, std::shared_ptr cmpd, std::shared_ptr uncC) : + AbstractDecoder(width, height, std::move(cmpd), std::move(uncC)) {} + + Error decode_tile(const HeifContext* context, + heif_item_id image_id, + std::shared_ptr& img, + uint32_t out_x0, uint32_t out_y0, + uint32_t image_width, uint32_t image_height, + uint32_t tile_x, uint32_t tile_y) override; + + void processTile(UncompressedBitReader& srcBits, uint32_t tile_row, uint32_t tile_column, + uint32_t out_x0, uint32_t out_y0); +}; + +#endif // UNCI_DECODER_MIXED_INTERLEAVE_H diff --git a/libheif/codecs/uncompressed/decoder_pixel_interleave.cc b/libheif/codecs/uncompressed/decoder_pixel_interleave.cc new file mode 100644 index 0000000000..978b9affcd --- /dev/null +++ b/libheif/codecs/uncompressed/decoder_pixel_interleave.cc @@ -0,0 +1,122 @@ +/* + * HEIF codec. + * Copyright (c) 2023 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 "decoder_pixel_interleave.h" +#include "context.h" +#include "error.h" + +#include +#include + + +Error PixelInterleaveDecoder::decode_tile(const HeifContext* context, + heif_item_id image_id, + std::shared_ptr& img, + uint32_t out_x0, uint32_t out_y0, + uint32_t image_width, uint32_t image_height, + uint32_t tile_x, uint32_t tile_y) +{ + if (m_tile_width == 0) { + return {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "Internal error: PixelInterleaveDecoder tile_width=0"}; + } + + // --- compute which file range we need to read for the tile + + uint32_t bits_per_row = 0; + for (uint32_t x = 0; x < m_tile_width; x++) { + uint32_t bits_per_pixel = 0; + + for (ChannelListEntry& entry : channelList) { + uint32_t bits_per_component = entry.bits_per_component_sample; + if (entry.component_alignment > 0) { + // start at byte boundary + bits_per_row = (bits_per_row + 7) & ~7U; + + uint32_t bytes_per_component = (bits_per_component + 7) / 8; + skip_to_alignment(bytes_per_component, entry.component_alignment); + bits_per_component = bytes_per_component * 8; + } + + bits_per_pixel += bits_per_component; + } + + if (m_uncC->get_pixel_size() != 0) { + uint32_t bytes_per_pixel = (bits_per_pixel + 7) / 8; + skip_to_alignment(bytes_per_pixel, m_uncC->get_pixel_size()); + bits_per_pixel = bytes_per_pixel * 8; + } + + bits_per_row += bits_per_pixel; + } + + uint32_t bytes_per_row = (bits_per_row + 7) / 8; + skip_to_alignment(bytes_per_row, m_uncC->get_row_align_size()); + + uint64_t total_tile_size = bytes_per_row * static_cast(m_tile_height); + if (m_uncC->get_tile_align_size() != 0) { + skip_to_alignment(total_tile_size, m_uncC->get_tile_align_size()); + } + + assert(m_tile_width > 0); + uint32_t tileIdx = tile_x + tile_y * (image_width / m_tile_width); + uint64_t tile_start_offset = total_tile_size * tileIdx; + + + // --- read required file range + + std::vector src_data; + Error err = get_compressed_image_data_uncompressed(context, image_id, &src_data, tile_start_offset, total_tile_size, tileIdx, nullptr); + //Error err = context->get_heif_file()->append_data_from_iloc(image_id, src_data, tile_start_offset, total_tile_size); + if (err) { + return err; + } + + UncompressedBitReader srcBits(src_data); + + processTile(srcBits, tile_y, tile_x, out_x0, out_y0); + + return Error::Ok; +} + +void PixelInterleaveDecoder::processTile(UncompressedBitReader& srcBits, uint32_t tile_row, uint32_t tile_column, uint32_t out_x0, uint32_t out_y0) +{ + for (uint32_t tile_y = 0; tile_y < m_tile_height; tile_y++) { + srcBits.markRowStart(); + for (uint32_t tile_x = 0; tile_x < m_tile_width; tile_x++) { + srcBits.markPixelStart(); + for (ChannelListEntry& entry : channelList) { + if (entry.use_channel) { + uint64_t dst_row_offset = entry.getDestinationRowOffset(0, tile_y + out_y0); + if (entry.component_alignment != 0) { + srcBits.skip_to_byte_boundary(); + int numPadBits = (entry.component_alignment * 8) - entry.bits_per_component_sample; + srcBits.skip_bits(numPadBits); + } + processComponentSample(srcBits, entry, dst_row_offset, 0, out_x0 + tile_x); + } + else { + srcBits.skip_bytes(entry.bytes_per_component_sample); + } + } + srcBits.handlePixelAlignment(m_uncC->get_pixel_size()); + } + srcBits.handleRowAlignment(m_uncC->get_row_align_size()); + } +} diff --git a/libheif/codecs/uncompressed/decoder_pixel_interleave.h b/libheif/codecs/uncompressed/decoder_pixel_interleave.h new file mode 100644 index 0000000000..b2fc053b16 --- /dev/null +++ b/libheif/codecs/uncompressed/decoder_pixel_interleave.h @@ -0,0 +1,65 @@ +/* + * HEIF codec. + * Copyright (c) 2023 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 UNCI_DECODER_PIXEL_INTERLEAVE_H +#define UNCI_DECODER_PIXEL_INTERLEAVE_H + +#include +#include +#include +#include +#include +#include +#include + +#include "common_utils.h" +#include "context.h" +#include "compression.h" +#include "error.h" +#include "libheif/heif.h" +#include "unc_types.h" +#include "unc_boxes.h" +#include "unc_codec.h" +#include "unc_dec.h" + +#include "decoder_abstract.h" +#include "decoder_component_interleave.h" +#include +#include + + +class PixelInterleaveDecoder : public AbstractDecoder +{ +public: + PixelInterleaveDecoder(uint32_t width, uint32_t height, std::shared_ptr cmpd, std::shared_ptr uncC) : + AbstractDecoder(width, height, std::move(cmpd), std::move(uncC)) {} + + Error decode_tile(const HeifContext* context, + heif_item_id image_id, + std::shared_ptr& img, + uint32_t out_x0, uint32_t out_y0, + uint32_t image_width, uint32_t image_height, + uint32_t tile_x, uint32_t tile_y) override; + + void processTile(UncompressedBitReader& srcBits, uint32_t tile_row, uint32_t tile_column, + uint32_t out_x0, uint32_t out_y0); +}; + +#endif // UNCI_DECODER_PIXEL_INTERLEAVE_H diff --git a/libheif/codecs/uncompressed/decoder_row_interleave.cc b/libheif/codecs/uncompressed/decoder_row_interleave.cc new file mode 100644 index 0000000000..b5efb6bfd3 --- /dev/null +++ b/libheif/codecs/uncompressed/decoder_row_interleave.cc @@ -0,0 +1,115 @@ +/* + * HEIF codec. + * Copyright (c) 2023 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 "decoder_row_interleave.h" +#include "context.h" +#include "error.h" + +#include +#include + + +Error RowInterleaveDecoder::decode_tile(const HeifContext* context, + heif_item_id image_id, + std::shared_ptr& img, + uint32_t out_x0, uint32_t out_y0, + uint32_t image_width, uint32_t image_height, + uint32_t tile_x, uint32_t tile_y) +{ + if (m_tile_width == 0) { + return {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "Internal error: RowInterleaveDecoder tile_width=0"}; + } + + // --- compute which file range we need to read for the tile + + uint32_t bits_per_row = 0; + for (ChannelListEntry& entry : channelList) { + uint32_t bits_per_component = entry.bits_per_component_sample; + if (entry.component_alignment > 0) { + // start at byte boundary + bits_per_row = (bits_per_row + 7) & ~7U; + + uint32_t bytes_per_component = (bits_per_component + 7) / 8; + skip_to_alignment(bytes_per_component, entry.component_alignment); + bits_per_component = bytes_per_component * 8; + } + + if (m_uncC->get_row_align_size() != 0) { + uint32_t bytes_this_row = (bits_per_component * m_tile_width + 7) / 8; + skip_to_alignment(bytes_this_row, m_uncC->get_row_align_size()); + bits_per_row += bytes_this_row * 8; + } + else { + bits_per_row += bits_per_component * m_tile_width; + } + + bits_per_row = (bits_per_row + 7) & ~7U; + } + + uint32_t bytes_per_row = (bits_per_row + 7) / 8; + if (m_uncC->get_row_align_size()) { + skip_to_alignment(bytes_per_row, m_uncC->get_row_align_size()); + } + + uint64_t total_tile_size = 0; + total_tile_size += bytes_per_row * static_cast(m_tile_height); + + if (m_uncC->get_tile_align_size() != 0) { + skip_to_alignment(total_tile_size, m_uncC->get_tile_align_size()); + } + + assert(m_tile_width > 0); + uint32_t tileIdx = tile_x + tile_y * (image_width / m_tile_width); + uint64_t tile_start_offset = total_tile_size * tileIdx; + + + // --- read required file range + + std::vector src_data; + Error err = get_compressed_image_data_uncompressed(context, image_id, &src_data, tile_start_offset, total_tile_size, tileIdx, nullptr); + //Error err = context->get_heif_file()->append_data_from_iloc(image_id, src_data, tile_start_offset, total_tile_size); + if (err) { + return err; + } + + UncompressedBitReader srcBits(src_data); + + processTile(srcBits, tile_y, tile_x, out_x0, out_y0); + + return Error::Ok; +} + +void RowInterleaveDecoder::processTile(UncompressedBitReader& srcBits, uint32_t tile_row, uint32_t tile_column, uint32_t out_x0, uint32_t out_y0) +{ + for (uint32_t tile_y = 0; tile_y < m_tile_height; tile_y++) { + for (ChannelListEntry& entry : channelList) { + srcBits.markRowStart(); + if (entry.use_channel) { + uint64_t dst_row_offset = entry.getDestinationRowOffset(0, tile_y + out_y0); + processComponentRow(entry, srcBits, dst_row_offset + out_x0 * entry.bytes_per_component_sample, 0); + } + else { + srcBits.skip_bytes(entry.bytes_per_tile_row_src); + } + srcBits.handleRowAlignment(m_uncC->get_row_align_size()); + } + } +} + diff --git a/libheif/codecs/uncompressed/decoder_row_interleave.h b/libheif/codecs/uncompressed/decoder_row_interleave.h new file mode 100644 index 0000000000..d6bff32008 --- /dev/null +++ b/libheif/codecs/uncompressed/decoder_row_interleave.h @@ -0,0 +1,48 @@ +/* + * HEIF codec. + * Copyright (c) 2023 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 UNCI_DECODER_ROW_INTERLEAVE_H +#define UNCI_DECODER_ROW_INTERLEAVE_H + +#include "decoder_abstract.h" +#include +#include + + +class RowInterleaveDecoder : public AbstractDecoder +{ +public: + RowInterleaveDecoder(uint32_t width, uint32_t height, + std::shared_ptr cmpd, std::shared_ptr uncC) : + AbstractDecoder(width, height, std::move(cmpd), std::move(uncC)) {} + + Error decode_tile(const HeifContext* context, + heif_item_id image_id, + std::shared_ptr& img, + uint32_t out_x0, uint32_t out_y0, + uint32_t image_width, uint32_t image_height, + uint32_t tile_x, uint32_t tile_y) override; + +private: + void processTile(UncompressedBitReader& srcBits, uint32_t tile_row, uint32_t tile_column, + uint32_t out_x0, uint32_t out_y0); +}; + +#endif // UNCI_DECODER_ROW_INTERLEAVE_H diff --git a/libheif/codecs/uncompressed/decoder_tile_component_interleave.cc b/libheif/codecs/uncompressed/decoder_tile_component_interleave.cc new file mode 100644 index 0000000000..4190b97ed0 --- /dev/null +++ b/libheif/codecs/uncompressed/decoder_tile_component_interleave.cc @@ -0,0 +1,131 @@ +/* + * HEIF codec. + * Copyright (c) 2023 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 "decoder_tile_component_interleave.h" +#include "context.h" +#include "error.h" + +#include +#include +#include + + +Error TileComponentInterleaveDecoder::decode_tile(const HeifContext* context, + heif_item_id image_id, + std::shared_ptr& img, + uint32_t out_x0, uint32_t out_y0, + uint32_t image_width, uint32_t image_height, + uint32_t tile_column, uint32_t tile_row) +{ + if (m_tile_width == 0) { + return {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "Internal error: TileComponentInterleaveDecoder tile_width=0"}; + } + if (m_tile_height == 0) { + return {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "Internal error: TileComponentInterleaveDecoder tile_height=0"}; + } + + // --- compute which file range we need to read for the tile + + std::map channel_tile_size; + + //uint64_t total_tile_size = 0; + + for (ChannelListEntry& entry : channelList) { + uint32_t bits_per_pixel = entry.bits_per_component_sample; + if (entry.component_alignment > 0) { + // start at byte boundary + //bits_per_row = (bits_per_row + 7) & ~7U; + + uint32_t bytes_per_component = (bits_per_pixel + 7) / 8; + skip_to_alignment(bytes_per_component, entry.component_alignment); + bits_per_pixel = bytes_per_component * 8; + } + + uint32_t bytes_per_row; + if (m_uncC->get_pixel_size() != 0) { // TODO: does pixel_size apply here? + uint32_t bytes_per_pixel = (bits_per_pixel + 7) / 8; + skip_to_alignment(bytes_per_pixel, m_uncC->get_pixel_size()); + bytes_per_row = bytes_per_pixel * m_tile_width; + } + else { + bytes_per_row = (bits_per_pixel * m_tile_width + 7) / 8; + } + + skip_to_alignment(bytes_per_row, m_uncC->get_row_align_size()); + + uint64_t component_tile_size = bytes_per_row * static_cast(m_tile_height); + + if (m_uncC->get_tile_align_size() != 0) { + skip_to_alignment(component_tile_size, m_uncC->get_tile_align_size()); + } + + channel_tile_size[entry.channel] = component_tile_size; + + //total_tile_size += component_tile_size; + } + + uint64_t component_start_offset = 0; + + assert(m_tile_width > 0); + assert(m_tile_height > 0); + + for (ChannelListEntry& entry : channelList) { + //processTile(srcBits, tile_y, tile_x, out_x0, out_y0); + + if (!entry.use_channel) { + //uint64_t bytes_per_component = entry.get_bytes_per_tile() * m_uncC->get_number_of_tile_columns() * m_uncC->get_number_of_tile_rows(); + //srcBits.skip_bytes((int)bytes_per_component); + + component_start_offset += channel_tile_size[entry.channel] * (m_width / m_tile_width) * (m_height / m_tile_height); + continue; + } + + // --- read required file range + + uint32_t tileIdx = tile_column + tile_row * (image_width / m_tile_width); + uint64_t tile_start_offset = component_start_offset + channel_tile_size[entry.channel] * tileIdx; + + std::vector src_data; + Error err = get_compressed_image_data_uncompressed(context, image_id, &src_data, tile_start_offset, channel_tile_size[entry.channel], tileIdx, nullptr); + //Error err = context->get_heif_file()->append_data_from_iloc(image_id, src_data, tile_start_offset, channel_tile_size[entry.channel]); + if (err) { + return err; + } + + UncompressedBitReader srcBits(src_data); + + srcBits.markTileStart(); + for (uint32_t tile_y = 0; tile_y < entry.tile_height; tile_y++) { + srcBits.markRowStart(); + uint64_t dst_row_offset = entry.getDestinationRowOffset(0, tile_y + out_y0); + processComponentRow(entry, srcBits, dst_row_offset + out_x0 * entry.bytes_per_component_sample, 0); + srcBits.handleRowAlignment(m_uncC->get_row_align_size()); + } + srcBits.handleTileAlignment(m_uncC->get_tile_align_size()); + + + component_start_offset += channel_tile_size[entry.channel] * (m_width / m_tile_width) * (m_height / m_tile_height); + } + + return Error::Ok; +} + + diff --git a/libheif/codecs/uncompressed/decoder_tile_component_interleave.h b/libheif/codecs/uncompressed/decoder_tile_component_interleave.h new file mode 100644 index 0000000000..590c18dfcb --- /dev/null +++ b/libheif/codecs/uncompressed/decoder_tile_component_interleave.h @@ -0,0 +1,44 @@ +/* + * HEIF codec. + * Copyright (c) 2023 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 UNCI_DECODER_TILE_COMPONENT_INTERLEAVE_H +#define UNCI_DECODER_TILE_COMPONENT_INTERLEAVE_H + +#include "decoder_abstract.h" +#include +#include + + +class TileComponentInterleaveDecoder : public AbstractDecoder +{ +public: + TileComponentInterleaveDecoder(uint32_t width, uint32_t height, + std::shared_ptr cmpd, std::shared_ptr uncC) : + AbstractDecoder(width, height, std::move(cmpd), std::move(uncC)) {} + + Error decode_tile(const HeifContext* context, + heif_item_id image_id, + std::shared_ptr& img, + uint32_t out_x0, uint32_t out_y0, + uint32_t image_width, uint32_t image_height, + uint32_t tile_column, uint32_t tile_row) override; +}; + +#endif // UNCI_DECODER_TILE_COMPONENT_INTERLEAVE_H diff --git a/libheif/codecs/uncompressed_box.cc b/libheif/codecs/uncompressed/unc_boxes.cc similarity index 50% rename from libheif/codecs/uncompressed_box.cc rename to libheif/codecs/uncompressed/unc_boxes.cc index b6afa359c0..2eed7c1314 100644 --- a/libheif/codecs/uncompressed_box.cc +++ b/libheif/codecs/uncompressed/unc_boxes.cc @@ -25,10 +25,12 @@ #include #include #include +#include #include "libheif/heif.h" -#include "uncompressed.h" -#include "uncompressed_box.h" +#include "libheif/heif_experimental.h" +#include "unc_types.h" +#include "unc_boxes.h" /** @@ -127,7 +129,7 @@ template const char* get_name(T val, const std::map } } -Error Box_cmpd::parse(BitstreamRange& range) +Error Box_cmpd::parse(BitstreamRange& range, const heif_security_limits* limits) { unsigned int component_count = range.read32(); @@ -194,19 +196,19 @@ Error Box_cmpd::write(StreamWriter& writer) const return Error::Ok; } -Error Box_uncC::parse(BitstreamRange& range) +Error Box_uncC::parse(BitstreamRange& range, const heif_security_limits* limits) { parse_full_box_header(range); m_profile = range.read32(); if (get_version() == 1) { - if (m_profile == fourcc_to_uint32("rgb3")) { + if (m_profile == fourcc("rgb3")) { Box_uncC::Component component0 = {0, 8, component_format_unsigned, 0}; add_component(component0); Box_uncC::Component component1 = {1, 8, component_format_unsigned, 0}; add_component(component1); Box_uncC::Component component2 = {2, 8, component_format_unsigned, 0}; add_component(component2); - } else if ((m_profile == fourcc_to_uint32("rgba")) || (m_profile == fourcc_to_uint32("abgr"))) { + } else if ((m_profile == fourcc("rgba")) || (m_profile == fourcc("abgr"))) { Box_uncC::Component component0 = {0, 8, component_format_unsigned, 0}; add_component(component0); Box_uncC::Component component1 = {1, 8, component_format_unsigned, 0}; @@ -260,9 +262,19 @@ Error Box_uncC::parse(BitstreamRange& range) m_tile_align_size = range.read32(); - m_num_tile_cols = range.read32() + 1; + uint32_t num_tile_cols_minus_one = range.read32(); + uint32_t num_tile_rows_minus_one = range.read32(); + if ((num_tile_cols_minus_one >= UINT32_MAX) || (num_tile_rows_minus_one >= UINT32_MAX)) { + std::stringstream sstr; + sstr << "Tiling size " << ((uint64_t)num_tile_cols_minus_one + 1) << " x " << ((uint64_t)num_tile_rows_minus_one + 1) << " exceeds the maximum allowed size " + << UINT32_MAX << " x " << UINT32_MAX; + return Error(heif_error_Memory_allocation_error, + heif_suberror_Security_limit_exceeded, + sstr.str()); + } + m_num_tile_cols = num_tile_cols_minus_one + 1; - m_num_tile_rows = range.read32() + 1; + m_num_tile_rows = num_tile_rows_minus_one + 1; } return range.get_error(); } @@ -276,15 +288,17 @@ std::string Box_uncC::dump(Indent& indent) const sstr << indent << "profile: " << m_profile; if (m_profile != 0) { - sstr << " (" << to_fourcc(m_profile) << ")"; - sstr << "\n"; + sstr << " (" << fourcc_to_string(m_profile) << ")"; } + sstr << "\n"; if (get_version() == 0) { for (const auto& component : m_components) { sstr << indent << "component_index: " << component.component_index << "\n"; + indent++; sstr << indent << "component_bit_depth: " << (int) component.component_bit_depth << "\n"; sstr << indent << "component_format: " << get_name(heif_uncompressed_component_format(component.component_format), sNames_uncompressed_component_format) << "\n"; sstr << indent << "component_align_size: " << (int) component.component_align_size << "\n"; + indent--; } sstr << indent << "sampling_type: " << get_name(heif_uncompressed_sampling_mode(m_sampling_type), sNames_uncompressed_sampling_mode) << "\n"; @@ -312,6 +326,7 @@ std::string Box_uncC::dump(Indent& indent) const return sstr.str(); } + Error Box_uncC::write(StreamWriter& writer) const { size_t box_start = reserve_box_header_space(writer); @@ -350,3 +365,331 @@ Error Box_uncC::write(StreamWriter& writer) const return Error::Ok; } + + +uint64_t Box_uncC::compute_tile_data_size_bytes(uint32_t tile_width, uint32_t tile_height) const +{ + if (m_profile != 0) { + switch (m_profile) { + case fourcc("rgba"): + return 4 * tile_width * tile_height; + + case fourcc("rgb3"): + return 3 * tile_width * tile_height; + + default: + assert(false); + return 0; + } + } + + switch (m_interleave_type) { + case interleave_mode_component: + case interleave_mode_pixel: { + uint32_t bytes_per_pixel = 0; + + for (const auto& comp : m_components) { + assert(comp.component_bit_depth % 8 == 0); // TODO: component sizes that are no multiples of bytes + bytes_per_pixel += comp.component_bit_depth / 8; + } + + return bytes_per_pixel * tile_width * tile_height; + } + default: + assert(false); + return 0; + } +} + + +Error Box_cmpC::parse(BitstreamRange& range, const heif_security_limits* limits) +{ + parse_full_box_header(range); + + if (get_version() != 0) { + return unsupported_version_error("cmpC"); + } + + m_compression_type = range.read32(); + m_compressed_unit_type = range.read8(); + return range.get_error(); +} + + +std::string Box_cmpC::dump(Indent& indent) const +{ + std::ostringstream sstr; + sstr << Box::dump(indent); + sstr << indent << "compression_type: " << fourcc_to_string(m_compression_type) << "\n"; + sstr << indent << "compressed_entity_type: " << (int)m_compressed_unit_type << "\n"; + return sstr.str(); +} + +Error Box_cmpC::write(StreamWriter& writer) const +{ + size_t box_start = reserve_box_header_space(writer); + + writer.write32(m_compression_type); + writer.write8(m_compressed_unit_type); + + prepend_header(writer, box_start); + + return Error::Ok; +} + + +Error Box_icef::parse(BitstreamRange& range, const heif_security_limits* limits) +{ + parse_full_box_header(range); + + if (get_version() != 0) { + return unsupported_version_error("icef"); + } + uint8_t codes = range.read8(); + uint8_t unit_offset_code = (codes & 0b11100000) >> 5; + uint8_t unit_size_code = (codes & 0b00011100) >> 2; + uint32_t num_compressed_units = range.read32(); + uint64_t implied_offset = 0; + for (uint32_t r = 0; r < num_compressed_units; r++) { + struct CompressedUnitInfo unitInfo; + if (unit_offset_code == 0) { + unitInfo.unit_offset = implied_offset; + } else if (unit_offset_code == 1) { + unitInfo.unit_offset = range.read16(); + } else if (unit_offset_code == 2) { + unitInfo.unit_offset = range.read24(); + } else if (unit_offset_code == 3) { + unitInfo.unit_offset = range.read32(); + } else if (unit_offset_code == 4) { + unitInfo.unit_offset = range.read64(); + } else { + return Error(heif_error_Usage_error, heif_suberror_Unsupported_parameter, "Unsupported icef unit offset code"); + } + if (unit_size_code == 0) { + unitInfo.unit_size = range.read8(); + } else if (unit_size_code == 1) { + unitInfo.unit_size = range.read16(); + } else if (unit_size_code == 2) { + unitInfo.unit_size = range.read24(); + } else if (unit_size_code == 3) { + unitInfo.unit_size = range.read32(); + } else if (unit_size_code == 4) { + unitInfo.unit_size = range.read64(); + } else { + return Error(heif_error_Usage_error, heif_suberror_Unsupported_parameter, "Unsupported icef unit size code"); + } + if (unitInfo.unit_size >= UINT64_MAX - implied_offset) { + return {heif_error_Invalid_input, heif_suberror_Invalid_parameter_value, "cumulative offsets too large for 64 bit file size"}; + } + implied_offset += unitInfo.unit_size; + if (range.get_error() != Error::Ok) { + return range.get_error(); + } + m_unit_infos.push_back(unitInfo); + } + return range.get_error(); +} + + +std::string Box_icef::dump(Indent& indent) const +{ + std::ostringstream sstr; + sstr << Box::dump(indent); + sstr << indent << "num_compressed_units: " << m_unit_infos.size() << "\n"; + for (CompressedUnitInfo unit_info: m_unit_infos) { + sstr << indent << "unit_offset: " << unit_info.unit_offset << ", unit_size: " << unit_info.unit_size << "\n"; + } + return sstr.str(); +} + +Error Box_icef::write(StreamWriter& writer) const +{ + // check that all units have a non-zero size + + for (const CompressedUnitInfo& unit_info: m_unit_infos) { + if (unit_info.unit_size == 0) { + return { + heif_error_Usage_error, + heif_suberror_Unspecified, + "tiled 'unci' image has an undefined tile." + }; + } + } + + size_t box_start = reserve_box_header_space(writer); + + uint8_t unit_offset_code = 1; + uint8_t unit_size_code = 0; + uint64_t implied_offset = 0; + bool can_use_implied_offsets = true; + for (const CompressedUnitInfo& unit_info: m_unit_infos) { + if (unit_info.unit_offset != implied_offset) { + can_use_implied_offsets = false; + } + if (unit_info.unit_size > (std::numeric_limits::max() - implied_offset)) { + can_use_implied_offsets = false; + } else { + implied_offset += unit_info.unit_size; + } + uint8_t required_offset_code = get_required_offset_code(unit_info.unit_offset); + if (required_offset_code > unit_offset_code) { + unit_offset_code = required_offset_code; + } + uint8_t required_size_code = get_required_size_code(unit_info.unit_size); + if (required_size_code > unit_size_code) { + unit_size_code = required_size_code; + } + } + if (can_use_implied_offsets) { + unit_offset_code = 0; + } + uint8_t code_bits = (uint8_t)((unit_offset_code << 5) | (unit_size_code << 2)); + writer.write8(code_bits); + writer.write32((uint32_t)m_unit_infos.size()); + for (CompressedUnitInfo unit_info: m_unit_infos) { + if (unit_offset_code == 0) { + // nothing + } else if (unit_offset_code == 1) { + writer.write16((uint16_t)unit_info.unit_offset); + } else if (unit_offset_code == 2) { + writer.write24((uint32_t)unit_info.unit_offset); + } else if (unit_offset_code == 3) { + writer.write32((uint32_t)unit_info.unit_offset); + } else { + writer.write64(unit_info.unit_offset); + } + if (unit_size_code == 0) { + writer.write8((uint8_t)unit_info.unit_size); + } else if (unit_size_code == 1) { + writer.write16((uint16_t)unit_info.unit_size); + } else if (unit_size_code == 2) { + writer.write24((uint32_t)unit_info.unit_size); + } else if (unit_size_code == 3) { + writer.write32((uint32_t)unit_info.unit_size); + } else { + writer.write64(unit_info.unit_size); + } + } + prepend_header(writer, box_start); + + return Error::Ok; +} + +static uint64_t MAX_OFFSET_UNIT_CODE_1 = std::numeric_limits::max(); +static uint64_t MAX_OFFSET_UNIT_CODE_2 = (1ULL << 24) - 1; +static uint64_t MAX_OFFSET_UNIT_CODE_3 = std::numeric_limits::max(); + +const uint8_t Box_icef::get_required_offset_code(uint64_t offset) const +{ + if (offset <= MAX_OFFSET_UNIT_CODE_1) { + return 1; + } + if (offset <= MAX_OFFSET_UNIT_CODE_2) { + return 2; + } + if (offset <= MAX_OFFSET_UNIT_CODE_3) { + return 3; + } + return 4; +} + +static uint64_t MAX_SIZE_UNIT_CODE_0 = std::numeric_limits::max(); +static uint64_t MAX_SIZE_UNIT_CODE_1 = std::numeric_limits::max(); +static uint64_t MAX_SIZE_UNIT_CODE_2 = (1ULL << 24) - 1; +static uint64_t MAX_SIZE_UNIT_CODE_3 = std::numeric_limits::max(); + +const uint8_t Box_icef::get_required_size_code(uint64_t size) const +{ + if (size <= MAX_SIZE_UNIT_CODE_0) { + return 0; + } + if (size <= MAX_SIZE_UNIT_CODE_1) { + return 1; + } + if (size <= MAX_SIZE_UNIT_CODE_2) { + return 2; + } + if (size <= MAX_SIZE_UNIT_CODE_3) { + return 3; + } + return 4; +} + + +Error Box_cpat::parse(BitstreamRange& range, const heif_security_limits* limits) +{ + parse_full_box_header(range); + + if (get_version() != 0) { + return unsupported_version_error("cpat"); + } + + m_pattern_width = range.read16(); + m_pattern_height = range.read16(); + + if (m_pattern_width == 0 || m_pattern_height == 0) { + return {heif_error_Invalid_input, + heif_suberror_Invalid_parameter_value, + "Zero Bayer pattern size."}; + } + + auto max_bayer_pattern_size = limits->max_bayer_pattern_pixels; + if (max_bayer_pattern_size && m_pattern_height > max_bayer_pattern_size / m_pattern_width) { + return {heif_error_Invalid_input, + heif_suberror_Security_limit_exceeded, + "Maximum Bayer pattern size exceeded."}; + } + + m_components.resize(m_pattern_width * m_pattern_height); + + for (uint16_t i = 0; i < m_pattern_height; i++) { + for (uint16_t j = 0; j < m_pattern_width; j++) { + PatternComponent component{}; + component.component_index = range.read32(); + component.component_gain = range.readFloat32(); + m_components[i] = component; + } + } + + return range.get_error(); +} + + +std::string Box_cpat::dump(Indent& indent) const +{ + std::ostringstream sstr; + + sstr << FullBox::dump(indent); + sstr << indent << "pattern_width: " << get_pattern_width() << "\n"; + sstr << indent << "pattern_height: " << get_pattern_height() << "\n"; + + for (const auto& component : m_components) { + sstr << indent << "component index: " << component.component_index << ", gain: " << component.component_gain << "\n"; + } + return sstr.str(); +} + + +Error Box_cpat::write(StreamWriter& writer) const +{ + size_t box_start = reserve_box_header_space(writer); + + if (m_pattern_width * m_pattern_height != m_components.size()) { + // needs to be rectangular + return {heif_error_Usage_error, + heif_suberror_Invalid_parameter_value, + "incorrect number of pattern components"}; + } + + writer.write16(m_pattern_width); + writer.write16(m_pattern_height); + + for (const auto& component : m_components) { + writer.write32(component.component_index); + writer.writeFloat32(component.component_gain); + } + + prepend_header(writer, box_start); + + return Error::Ok; +} diff --git a/libheif/codecs/uncompressed_box.h b/libheif/codecs/uncompressed/unc_boxes.h similarity index 58% rename from libheif/codecs/uncompressed_box.h rename to libheif/codecs/uncompressed/unc_boxes.h index 98ae3da60b..294157987f 100644 --- a/libheif/codecs/uncompressed_box.h +++ b/libheif/codecs/uncompressed/unc_boxes.h @@ -19,12 +19,12 @@ */ -#ifndef LIBHEIF_UNCOMPRESSED_BOX_H -#define LIBHEIF_UNCOMPRESSED_BOX_H +#ifndef LIBHEIF_UNC_BOXES_H +#define LIBHEIF_UNC_BOXES_H #include "box.h" #include "bitstream.h" -#include "uncompressed.h" +#include "unc_types.h" #include #include @@ -65,7 +65,7 @@ class Box_cmpd : public Box } protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits* limits) override; std::vector m_components; }; @@ -80,6 +80,8 @@ class Box_uncC : public FullBox set_short_type(fourcc("uncC")); } + bool is_essential() const override { return true; } + void derive_box_version() override {}; std::string dump(Indent&) const override; @@ -199,10 +201,14 @@ class Box_uncC : public FullBox m_num_tile_rows = num_tile_rows; } + uint32_t get_number_of_tiles() const { return m_num_tile_rows * m_num_tile_rows; } + + uint64_t compute_tile_data_size_bytes(uint32_t tile_width, uint32_t tile_height) const; + protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits* limits) override; - uint32_t m_profile = 0; // not compliant to any profile + uint32_t m_profile = 0; // 0 = not compliant to any profile std::vector m_components; uint8_t m_sampling_type = sampling_mode_no_subsampling; // no subsampling @@ -220,4 +226,142 @@ class Box_uncC : public FullBox uint32_t m_num_tile_rows = 1; }; -#endif //LIBHEIF_UNCOMPRESSED_BOX_H + +enum heif_cmpC_compressed_unit_type { + heif_cmpC_compressed_unit_type_full_item = 0, + heif_cmpC_compressed_unit_type_image = 1, + heif_cmpC_compressed_unit_type_image_tile = 2, + heif_cmpC_compressed_unit_type_image_row = 3, + heif_cmpC_compressed_unit_type_image_pixel = 4 +}; + +/** + * Generic compression configuration box (cmpC). + * + * This is from ISO/IEC 23001-17 Amd 2. + */ +class Box_cmpC : public FullBox +{ +public: + Box_cmpC() + { + set_short_type(fourcc("cmpC")); + } + + std::string dump(Indent&) const override; + + uint32_t get_compression_type() const { return m_compression_type; } + + heif_cmpC_compressed_unit_type get_compressed_unit_type() const { return (heif_cmpC_compressed_unit_type) m_compressed_unit_type; } + + void set_compression_type(uint32_t type) { m_compression_type = type; } + + void set_compressed_unit_type(heif_cmpC_compressed_unit_type type) { m_compressed_unit_type = type; } + + Error write(StreamWriter& writer) const override; + +protected: + Error parse(BitstreamRange& range, const heif_security_limits* limits) override; + + uint32_t m_compression_type; + uint8_t m_compressed_unit_type; +}; + +/** + * Generically compressed units item info (icef). + * + * This describes the units of compressed data for an item. + * + * The box is from ISO/IEC 23001-17 Amd 2. + */ +class Box_icef : public FullBox +{ +public: + Box_icef() + { + set_short_type(fourcc("icef")); + } + + struct CompressedUnitInfo + { + uint64_t unit_offset = 0; + uint64_t unit_size = 0; + }; + + const std::vector& get_units() const { return m_unit_infos; } + + void add_component(const CompressedUnitInfo& unit_info) + { + m_unit_infos.push_back(unit_info); + } + + void set_component(uint32_t tile_idx, const CompressedUnitInfo& unit_info) + { + if (tile_idx >= m_unit_infos.size()) { + m_unit_infos.resize(tile_idx+1); + } + + m_unit_infos[tile_idx] = unit_info; + } + + std::string dump(Indent&) const override; + + Error write(StreamWriter& writer) const override; + +protected: + Error parse(BitstreamRange& range, const heif_security_limits* limits) override; + + std::vector m_unit_infos; + +private: + const uint8_t get_required_offset_code(uint64_t offset) const; + const uint8_t get_required_size_code(uint64_t size) const; +}; + + +/** + * Component pattern definition box (cpat). + * + * The component pattern is used when representing filter array + * data, such as Bayer. It defines the filter mask in the raw + * data. + * + * This is from ISO/IEC 23001-17 Section 6.1.3. + */ +class Box_cpat : public FullBox +{ +public: + Box_cpat() + { + set_short_type(fourcc("cpat")); + } + + struct PatternComponent + { + uint32_t component_index; + float component_gain; + }; + + uint16_t get_pattern_width() const + { + return m_pattern_width; + } + + uint16_t get_pattern_height() const + { + return m_pattern_height; + } + + std::string dump(Indent&) const override; + + Error write(StreamWriter& writer) const override; + +protected: + Error parse(BitstreamRange& range, const heif_security_limits* limits) override; + + uint16_t m_pattern_width; + uint16_t m_pattern_height; + std::vector m_components; +}; + +#endif //LIBHEIF_UNC_BOXES_H diff --git a/libheif/codecs/uncompressed/unc_codec.cc b/libheif/codecs/uncompressed/unc_codec.cc new file mode 100644 index 0000000000..8718cbc5fe --- /dev/null +++ b/libheif/codecs/uncompressed/unc_codec.cc @@ -0,0 +1,831 @@ +/* + * HEIF codec. + * Copyright (c) 2023 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 "unc_codec.h" + +#include "common_utils.h" +#include "context.h" +#include "error.h" +#include "libheif/heif.h" +#include "unc_types.h" +#include "unc_boxes.h" + +#include "decoder_abstract.h" +#include "decoder_component_interleave.h" +#include "decoder_pixel_interleave.h" +#include "decoder_mixed_interleave.h" +#include "decoder_row_interleave.h" +#include "decoder_tile_component_interleave.h" + +#include +#include +#include +#include +#include "security_limits.h" + + +bool isKnownUncompressedFrameConfigurationBoxProfile(const std::shared_ptr& uncC) +{ + return ((uncC != nullptr) && (uncC->get_version() == 1) && ((uncC->get_profile() == fourcc("rgb3")) || (uncC->get_profile() == fourcc("rgba")) || (uncC->get_profile() == fourcc("abgr")))); +} + + +static Error uncompressed_image_type_is_supported(const std::shared_ptr& uncC, + const std::shared_ptr& cmpd) +{ + if (isKnownUncompressedFrameConfigurationBoxProfile(uncC)) { + return Error::Ok; + } + if (!cmpd) { + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + "Missing required cmpd box (no match in uncC box) for uncompressed codec"); + } + + for (Box_uncC::Component component : uncC->get_components()) { + uint16_t component_index = component.component_index; + uint16_t component_type = cmpd->get_components()[component_index].component_type; + if ((component_type > 7) && (component_type != component_type_padded) && (component_type != component_type_filter_array)) { + std::stringstream sstr; + sstr << "Uncompressed image with component_type " << ((int) component_type) << " is not implemented yet"; + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + sstr.str()); + } + + if ((component.component_bit_depth > 16)) { + std::stringstream sstr; + sstr << "Uncompressed image with component_bit_depth " << ((int) component.component_bit_depth) << " is not implemented yet"; + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + sstr.str()); + } + if (component.component_format != component_format_unsigned) { + std::stringstream sstr; + sstr << "Uncompressed image with component_format " << ((int) component.component_format) << " is not implemented yet"; + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + sstr.str()); + } + if (component.component_align_size > 2) { + std::stringstream sstr; + sstr << "Uncompressed image with component_align_size " << ((int) component.component_align_size) << " is not implemented yet"; + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + sstr.str()); + } + } + if ((uncC->get_sampling_type() != sampling_mode_no_subsampling) + && (uncC->get_sampling_type() != sampling_mode_422) + && (uncC->get_sampling_type() != sampling_mode_420) + ) { + std::stringstream sstr; + sstr << "Uncompressed sampling_type of " << ((int) uncC->get_sampling_type()) << " is not implemented yet"; + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + sstr.str()); + } + if ((uncC->get_interleave_type() != interleave_mode_component) + && (uncC->get_interleave_type() != interleave_mode_pixel) + && (uncC->get_interleave_type() != interleave_mode_mixed) + && (uncC->get_interleave_type() != interleave_mode_row) + && (uncC->get_interleave_type() != interleave_mode_tile_component) + ) { + std::stringstream sstr; + sstr << "Uncompressed interleave_type of " << ((int) uncC->get_interleave_type()) << " is not implemented yet"; + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + sstr.str()); + } + // Validity checks per ISO/IEC 23001-17 Section 5.2.1.5.3 + if (uncC->get_sampling_type() == sampling_mode_422) { + // We check Y Cb and Cr appear in the chroma test + // TODO: error for tile width not multiple of 2 + if ((uncC->get_interleave_type() != interleave_mode_component) + && (uncC->get_interleave_type() != interleave_mode_mixed) + && (uncC->get_interleave_type() != interleave_mode_multi_y)) { + std::stringstream sstr; + sstr << "YCbCr 4:2:2 subsampling is only valid with component, mixed or multi-Y interleave mode (ISO/IEC 23001-17 5.2.1.5.3)."; + return Error(heif_error_Invalid_input, + heif_suberror_Invalid_parameter_value, + sstr.str()); + } + if ((uncC->get_row_align_size() != 0) && (uncC->get_interleave_type() == interleave_mode_component)) { + if (uncC->get_row_align_size() % 2 != 0) { + std::stringstream sstr; + sstr << "YCbCr 4:2:2 subsampling with component interleave requires row_align_size to be a multiple of 2 (ISO/IEC 23001-17 5.2.1.5.3)."; + return Error(heif_error_Invalid_input, + heif_suberror_Invalid_parameter_value, + sstr.str()); + } + } + if (uncC->get_tile_align_size() != 0) { + if (uncC->get_tile_align_size() % 2 != 0) { + std::stringstream sstr; + sstr << "YCbCr 4:2:2 subsampling requires tile_align_size to be a multiple of 2 (ISO/IEC 23001-17 5.2.1.5.3)."; + return Error(heif_error_Invalid_input, + heif_suberror_Invalid_parameter_value, + sstr.str()); + } + } + } + // Validity checks per ISO/IEC 23001-17 Section 5.2.1.5.4 + if (uncC->get_sampling_type() == sampling_mode_422) { + // We check Y Cb and Cr appear in the chroma test + // TODO: error for tile width not multiple of 2 + if ((uncC->get_interleave_type() != interleave_mode_component) + && (uncC->get_interleave_type() != interleave_mode_mixed)) { + std::stringstream sstr; + sstr << "YCbCr 4:2:0 subsampling is only valid with component or mixed interleave mode (ISO/IEC 23001-17 5.2.1.5.4)."; + return Error(heif_error_Invalid_input, + heif_suberror_Invalid_parameter_value, + sstr.str()); + } + if ((uncC->get_row_align_size() != 0) && (uncC->get_interleave_type() == interleave_mode_component)) { + if (uncC->get_row_align_size() % 2 != 0) { + std::stringstream sstr; + sstr << "YCbCr 4:2:2 subsampling with component interleave requires row_align_size to be a multiple of 2 (ISO/IEC 23001-17 5.2.1.5.4)."; + return Error(heif_error_Invalid_input, + heif_suberror_Invalid_parameter_value, + sstr.str()); + } + } + if (uncC->get_tile_align_size() != 0) { + if (uncC->get_tile_align_size() % 4 != 0) { + std::stringstream sstr; + sstr << "YCbCr 4:2:2 subsampling requires tile_align_size to be a multiple of 4 (ISO/IEC 23001-17 5.2.1.5.3)."; + return Error(heif_error_Invalid_input, + heif_suberror_Invalid_parameter_value, + sstr.str()); + } + } + } + if ((uncC->get_interleave_type() == interleave_mode_mixed) && (uncC->get_sampling_type() == sampling_mode_no_subsampling)) { + std::stringstream sstr; + sstr << "Interleave interleave mode is not valid with subsampling mode (ISO/IEC 23001-17 5.2.1.6.4)."; + return Error(heif_error_Invalid_input, + heif_suberror_Invalid_parameter_value, + sstr.str()); + } + if ((uncC->get_interleave_type() == interleave_mode_multi_y) + && ((uncC->get_sampling_type() != sampling_mode_422) && (uncC->get_sampling_type() != sampling_mode_411))) { + std::stringstream sstr; + sstr << "Multi-Y interleave mode is only valid with 4:2:2 and 4:1:1 subsampling modes (ISO/IEC 23001-17 5.2.1.6.7)."; + return Error(heif_error_Invalid_input, + heif_suberror_Invalid_parameter_value, + sstr.str()); + } + // TODO: throw error if mixed and Cb and Cr are not adjacent. + + if (uncC->get_block_size() != 0) { + std::stringstream sstr; + sstr << "Uncompressed block_size of " << ((int) uncC->get_block_size()) << " is not implemented yet"; + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + sstr.str()); + } +/* + // TODO: check if this really works... if (uncC->is_components_little_endian()) { + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + "Uncompressed components_little_endian == 1 is not implemented yet"); + } + */ + if (uncC->is_block_pad_lsb()) { + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + "Uncompressed block_pad_lsb == 1 is not implemented yet"); + } + if (uncC->is_block_little_endian()) { + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + "Uncompressed block_little_endian == 1 is not implemented yet"); + } + if (uncC->is_block_reversed()) { + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + "Uncompressed block_reversed == 1 is not implemented yet"); + } + if ((uncC->get_pixel_size() != 0) && ((uncC->get_interleave_type() != interleave_mode_pixel) && (uncC->get_interleave_type() != interleave_mode_multi_y))) { + std::stringstream sstr; + sstr << "Uncompressed pixel_size of " << ((int) uncC->get_pixel_size()) << " is only valid with interleave_type 1 or 5 (ISO/IEC 23001-17 5.2.1.7)"; + return Error(heif_error_Invalid_input, + heif_suberror_Invalid_parameter_value, + sstr.str()); + } + return Error::Ok; +} + + +Error UncompressedImageCodec::get_heif_chroma_uncompressed(const std::shared_ptr& uncC, + const std::shared_ptr& cmpd, + heif_chroma* out_chroma, heif_colorspace* out_colourspace) +{ + *out_chroma = heif_chroma_undefined; + *out_colourspace = heif_colorspace_undefined; + + Error error = check_header_validity(std::nullopt, cmpd, uncC); + if (error) { + return error; + } + + if (isKnownUncompressedFrameConfigurationBoxProfile(uncC)) { + *out_chroma = heif_chroma_444; + *out_colourspace = heif_colorspace_RGB; + return Error::Ok; + } + + // each 1-bit represents an existing component in the image + uint16_t componentSet = 0; + + for (Box_uncC::Component component : uncC->get_components()) { + uint16_t component_index = component.component_index; + uint16_t component_type = cmpd->get_components()[component_index].component_type; + + if (component_type > component_type_max_valid) { + std::stringstream sstr; + sstr << "a component_type > " << component_type_max_valid << " is not supported"; + return {heif_error_Unsupported_feature, heif_suberror_Invalid_parameter_value, sstr.str()}; + } + if (component_type == component_type_padded) { + // not relevant for determining chroma + continue; + } + componentSet |= (1 << component_type); + } + + if (componentSet == ((1 << component_type_red) | (1 << component_type_green) | (1 << component_type_blue)) || + componentSet == ((1 << component_type_red) | (1 << component_type_green) | (1 << component_type_blue) | (1 << component_type_alpha))) { + *out_chroma = heif_chroma_444; + *out_colourspace = heif_colorspace_RGB; + } + + if (componentSet == ((1 << component_type_Y) | (1 << component_type_Cb) | (1 << component_type_Cr))) { + switch (uncC->get_sampling_type()) { + case sampling_mode_no_subsampling: + *out_chroma = heif_chroma_444; + break; + case sampling_mode_422: + *out_chroma = heif_chroma_422; + break; + case sampling_mode_420: + *out_chroma = heif_chroma_420; + break; + } + *out_colourspace = heif_colorspace_YCbCr; + } + + if (componentSet == ((1 << component_type_monochrome)) || componentSet == ((1 << component_type_monochrome) | (1 << component_type_alpha))) { + // mono or mono + alpha input, mono output. + *out_chroma = heif_chroma_monochrome; + *out_colourspace = heif_colorspace_monochrome; + } + + if (componentSet == (1 << component_type_filter_array)) { + // TODO - we should look up the components + *out_chroma = heif_chroma_monochrome; + *out_colourspace = heif_colorspace_monochrome; + } + + // TODO: more combinations + + if (*out_chroma == heif_chroma_undefined) { + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + "Could not determine chroma"); + } + else if (*out_colourspace == heif_colorspace_undefined) { + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + "Could not determine colourspace"); + } + else { + return Error::Ok; + } +} + +bool map_uncompressed_component_to_channel(const std::shared_ptr& cmpd, + const std::shared_ptr& uncC, + Box_uncC::Component component, + heif_channel* channel) +{ + uint16_t component_index = component.component_index; + if (isKnownUncompressedFrameConfigurationBoxProfile(uncC)) { + if (uncC->get_profile() == fourcc("rgb3")) { + switch (component_index) { + case 0: + *channel = heif_channel_R; + return true; + case 1: + *channel = heif_channel_G; + return true; + case 2: + *channel = heif_channel_B; + return true; + } + } + else if (uncC->get_profile() == fourcc("rgba")) { + switch (component_index) { + case 0: + *channel = heif_channel_R; + return true; + case 1: + *channel = heif_channel_G; + return true; + case 2: + *channel = heif_channel_B; + return true; + case 3: + *channel = heif_channel_Alpha; + return true; + } + } + else if (uncC->get_profile() == fourcc("abgr")) { + switch (component_index) { + case 0: + *channel = heif_channel_Alpha; + return true; + case 1: + *channel = heif_channel_B; + return true; + case 2: + *channel = heif_channel_G; + return true; + case 3: + *channel = heif_channel_R; + return true; + } + } + } + uint16_t component_type = cmpd->get_components()[component_index].component_type; + + switch (component_type) { + case component_type_monochrome: + *channel = heif_channel_Y; + return true; + case component_type_Y: + *channel = heif_channel_Y; + return true; + case component_type_Cb: + *channel = heif_channel_Cb; + return true; + case component_type_Cr: + *channel = heif_channel_Cr; + return true; + case component_type_red: + *channel = heif_channel_R; + return true; + case component_type_green: + *channel = heif_channel_G; + return true; + case component_type_blue: + *channel = heif_channel_B; + return true; + case component_type_alpha: + *channel = heif_channel_Alpha; + return true; + case component_type_filter_array: + // TODO: this is just a temporary hack + *channel = heif_channel_Y; + return true; + case component_type_padded: + return false; + default: + return false; + } +} + + + +static AbstractDecoder* makeDecoder(uint32_t width, uint32_t height, const std::shared_ptr& cmpd, const std::shared_ptr& uncC) +{ + switch (uncC->get_interleave_type()) { + case interleave_mode_component: + return new ComponentInterleaveDecoder(width, height, cmpd, uncC); + case interleave_mode_pixel: + return new PixelInterleaveDecoder(width, height, cmpd, uncC); + case interleave_mode_mixed: + return new MixedInterleaveDecoder(width, height, cmpd, uncC); + case interleave_mode_row: + return new RowInterleaveDecoder(width, height, cmpd, uncC); + case interleave_mode_tile_component: + return new TileComponentInterleaveDecoder(width, height, cmpd, uncC); + default: + return nullptr; + } +} + + +Result> UncompressedImageCodec::create_image(const std::shared_ptr cmpd, + const std::shared_ptr uncC, + uint32_t width, + uint32_t height) +{ + auto img = std::make_shared(); + heif_chroma chroma = heif_chroma_undefined; + heif_colorspace colourspace = heif_colorspace_undefined; + + Error error = get_heif_chroma_uncompressed(uncC, cmpd, &chroma, &colourspace); + if (error) { + return error; + } + img->create(width, height, + colourspace, + chroma); + + for (Box_uncC::Component component : uncC->get_components()) { + heif_channel channel; + if (map_uncompressed_component_to_channel(cmpd, uncC, component, &channel)) { + if (img->has_channel(channel)) { + return Error{heif_error_Unsupported_feature, + heif_suberror_Unspecified, + "Cannot generate image with several similar heif_channels."}; + } + + if ((channel == heif_channel_Cb) || (channel == heif_channel_Cr)) { + img->add_plane(channel, (width / chroma_h_subsampling(chroma)), (height / chroma_v_subsampling(chroma)), component.component_bit_depth); + } + else { + img->add_plane(channel, width, height, component.component_bit_depth); + } + } + } + + return img; +} + + +Error UncompressedImageCodec::decode_uncompressed_image_tile(const HeifContext* context, + heif_item_id ID, + std::shared_ptr& img, + uint32_t tile_x0, uint32_t tile_y0) +{ + auto file = context->get_heif_file(); + std::shared_ptr ispe = file->get_property(ID); + std::shared_ptr cmpd = file->get_property(ID); + std::shared_ptr uncC = file->get_property(ID); + + Error error = check_header_validity(ispe, cmpd, uncC); + if (error) { + return error; + } + + uint32_t tile_width = ispe->get_width() / uncC->get_number_of_tile_columns(); + uint32_t tile_height = ispe->get_height() / uncC->get_number_of_tile_rows(); + + Result> createImgResult = create_image(cmpd, uncC, tile_width, tile_height); + if (createImgResult.error) { + return createImgResult.error; + } + + img = createImgResult.value; + + + AbstractDecoder* decoder = makeDecoder(ispe->get_width(), ispe->get_height(), cmpd, uncC); + if (decoder == nullptr) { + std::stringstream sstr; + sstr << "Uncompressed interleave_type of " << ((int) uncC->get_interleave_type()) << " is not implemented yet"; + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + sstr.str()); + } + + decoder->buildChannelList(img); + + Error result = decoder->decode_tile(context, ID, img, 0, 0, + ispe->get_width(), ispe->get_height(), + tile_x0, tile_y0); + delete decoder; + return result; +} + + +Error UncompressedImageCodec::check_header_validity(std::optional> ispe, + const std::shared_ptr& cmpd, + const std::shared_ptr& uncC) +{ + // if we miss a required box, show error + + if (!uncC) { + return {heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + "Missing required uncC box for uncompressed codec"}; + } + + if (!cmpd && (uncC->get_version() != 1)) { + return {heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + "Missing required cmpd or uncC version 1 box for uncompressed codec"}; + } + + if (cmpd) { + for (const auto& comp : uncC->get_components()) { + if (comp.component_index > cmpd->get_components().size()) { + return {heif_error_Invalid_input, + heif_suberror_Unspecified, + "Invalid component index in uncC box"}; + } + } + } + + + if (ispe) { + if (!*ispe) { + return {heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + "Missing required ispe box for uncompressed codec"}; + } + + if (uncC->get_number_of_tile_rows() > (*ispe)->get_height() || + uncC->get_number_of_tile_columns() > (*ispe)->get_width()) { + return {heif_error_Invalid_input, + heif_suberror_Unspecified, + "More tiles than pixels in uncC box"}; + } + + if ((*ispe)->get_height() % uncC->get_number_of_tile_rows() != 0 || + (*ispe)->get_width() % uncC->get_number_of_tile_columns() != 0) { + return {heif_error_Invalid_input, + heif_suberror_Unspecified, + "Invalid tile size (image size not a multiple of the tile size)"}; + } + } + + return Error::Ok; +} + + +Error UncompressedImageCodec::decode_uncompressed_image(const HeifContext* context, + heif_item_id ID, + std::shared_ptr& img) +{ + // Get the properties for this item + // We need: ispe, cmpd, uncC + std::vector> item_properties; + Error error = context->get_heif_file()->get_properties(ID, item_properties); + if (error) { + return error; + } + + std::shared_ptr ispe = context->get_heif_file()->get_property(ID); + std::shared_ptr cmpd = context->get_heif_file()->get_property(ID); + std::shared_ptr uncC = context->get_heif_file()->get_property(ID); + + error = check_header_validity(ispe, cmpd, uncC); + if (error) { + return error; + } + + // check if we support the type of image + + error = uncompressed_image_type_is_supported(uncC, cmpd); // TODO TODO TODO + if (error) { + return error; + } + + assert(ispe); + uint32_t width = ispe->get_width(); + uint32_t height = ispe->get_height(); + error = check_for_valid_image_size(context->get_security_limits(), width, height); + if (error) { + return error; + } + + Result> createImgResult = create_image(cmpd, uncC, width, height); + if (createImgResult.error) { + return createImgResult.error; + } + else { + img = *createImgResult; + } + + AbstractDecoder* decoder = makeDecoder(width, height, cmpd, uncC); + if (decoder == nullptr) { + std::stringstream sstr; + sstr << "Uncompressed interleave_type of " << ((int) uncC->get_interleave_type()) << " is not implemented yet"; + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + sstr.str()); + } + + decoder->buildChannelList(img); + + uint32_t tile_width = width / uncC->get_number_of_tile_columns(); + uint32_t tile_height = height / uncC->get_number_of_tile_rows(); + + for (uint32_t tile_y0 = 0; tile_y0 < height; tile_y0 += tile_height) + for (uint32_t tile_x0 = 0; tile_x0 < width; tile_x0 += tile_width) { + error = decoder->decode_tile(context, ID, img, tile_x0, tile_y0, + width, height, + tile_x0 / tile_width, tile_y0 / tile_height); + if (error) { + delete decoder; + return error; + } + } + + //Error result = decoder->decode(source_data, img); + delete decoder; + return Error::Ok; +} + +Error fill_cmpd_and_uncC(std::shared_ptr& cmpd, + std::shared_ptr& uncC, + const std::shared_ptr& image, + const heif_unci_image_parameters* parameters) +{ + uint32_t nTileColumns = parameters->image_width / parameters->tile_width; + uint32_t nTileRows = parameters->image_height / parameters->tile_height; + + const heif_colorspace colourspace = image->get_colorspace(); + if (colourspace == heif_colorspace_YCbCr) { + if (!(image->has_channel(heif_channel_Y) && image->has_channel(heif_channel_Cb) && image->has_channel(heif_channel_Cr))) { + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + "Invalid colourspace / channel combination - YCbCr"); + } + Box_cmpd::Component yComponent = {component_type_Y}; + cmpd->add_component(yComponent); + Box_cmpd::Component cbComponent = {component_type_Cb}; + cmpd->add_component(cbComponent); + Box_cmpd::Component crComponent = {component_type_Cr}; + cmpd->add_component(crComponent); + uint8_t bpp_y = image->get_bits_per_pixel(heif_channel_Y); + Box_uncC::Component component0 = {0, bpp_y, component_format_unsigned, 0}; + uncC->add_component(component0); + uint8_t bpp_cb = image->get_bits_per_pixel(heif_channel_Cb); + Box_uncC::Component component1 = {1, bpp_cb, component_format_unsigned, 0}; + uncC->add_component(component1); + uint8_t bpp_cr = image->get_bits_per_pixel(heif_channel_Cr); + Box_uncC::Component component2 = {2, bpp_cr, component_format_unsigned, 0}; + uncC->add_component(component2); + if (image->get_chroma_format() == heif_chroma_444) { + uncC->set_sampling_type(sampling_mode_no_subsampling); + } + else if (image->get_chroma_format() == heif_chroma_422) { + uncC->set_sampling_type(sampling_mode_422); + } + else if (image->get_chroma_format() == heif_chroma_420) { + uncC->set_sampling_type(sampling_mode_420); + } + else { + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + "Unsupported YCbCr sub-sampling type"); + } + uncC->set_interleave_type(interleave_mode_component); + uncC->set_block_size(0); + uncC->set_components_little_endian(false); + uncC->set_block_pad_lsb(false); + uncC->set_block_little_endian(false); + uncC->set_block_reversed(false); + uncC->set_pad_unknown(false); + uncC->set_pixel_size(0); + uncC->set_row_align_size(0); + uncC->set_tile_align_size(0); + uncC->set_number_of_tile_columns(nTileColumns); + uncC->set_number_of_tile_rows(nTileRows); + } + else if (colourspace == heif_colorspace_RGB) { + if (!((image->get_chroma_format() == heif_chroma_444) || + (image->get_chroma_format() == heif_chroma_interleaved_RGB) || + (image->get_chroma_format() == heif_chroma_interleaved_RGBA) || + (image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_BE) || + (image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_LE) || + (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_BE) || + (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE))) { + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + "Unsupported colourspace / chroma combination - RGB"); + } + Box_cmpd::Component rComponent = {component_type_red}; + cmpd->add_component(rComponent); + Box_cmpd::Component gComponent = {component_type_green}; + cmpd->add_component(gComponent); + Box_cmpd::Component bComponent = {component_type_blue}; + cmpd->add_component(bComponent); + if ((image->get_chroma_format() == heif_chroma_interleaved_RGBA) || + (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_BE) || + (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE) || + (image->has_channel(heif_channel_Alpha))) { + Box_cmpd::Component alphaComponent = {component_type_alpha}; + cmpd->add_component(alphaComponent); + } + if ((image->get_chroma_format() == heif_chroma_interleaved_RGB) || + (image->get_chroma_format() == heif_chroma_interleaved_RGBA) || + (image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_BE) || + (image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_LE) || + (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_BE) || + (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE)) { + uncC->set_interleave_type(interleave_mode_pixel); + int bpp = image->get_bits_per_pixel(heif_channel_interleaved); + uint8_t component_align = 1; + if (bpp == 8) { + component_align = 0; + } + else if (bpp > 8) { + component_align = 2; + } + Box_uncC::Component component0 = {0, (uint8_t) (bpp), component_format_unsigned, component_align}; + uncC->add_component(component0); + Box_uncC::Component component1 = {1, (uint8_t) (bpp), component_format_unsigned, component_align}; + uncC->add_component(component1); + Box_uncC::Component component2 = {2, (uint8_t) (bpp), component_format_unsigned, component_align}; + uncC->add_component(component2); + if ((image->get_chroma_format() == heif_chroma_interleaved_RGBA) || + (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_BE) || + (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE)) { + Box_uncC::Component component3 = { + 3, (uint8_t) (bpp), component_format_unsigned, component_align}; + uncC->add_component(component3); + } + } + else { + uncC->set_interleave_type(interleave_mode_component); + int bpp_red = image->get_bits_per_pixel(heif_channel_R); + Box_uncC::Component component0 = {0, (uint8_t) (bpp_red), component_format_unsigned, 0}; + uncC->add_component(component0); + int bpp_green = image->get_bits_per_pixel(heif_channel_G); + Box_uncC::Component component1 = {1, (uint8_t) (bpp_green), component_format_unsigned, 0}; + uncC->add_component(component1); + int bpp_blue = image->get_bits_per_pixel(heif_channel_B); + Box_uncC::Component component2 = {2, (uint8_t) (bpp_blue), component_format_unsigned, 0}; + uncC->add_component(component2); + if (image->has_channel(heif_channel_Alpha)) { + int bpp_alpha = image->get_bits_per_pixel(heif_channel_Alpha); + Box_uncC::Component component3 = {3, (uint8_t) (bpp_alpha), component_format_unsigned, 0}; + uncC->add_component(component3); + } + } + uncC->set_sampling_type(sampling_mode_no_subsampling); + uncC->set_block_size(0); + if ((image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_LE) || + (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE)) { + uncC->set_components_little_endian(true); + } + else { + uncC->set_components_little_endian(false); + } + uncC->set_block_pad_lsb(false); + uncC->set_block_little_endian(false); + uncC->set_block_reversed(false); + uncC->set_pad_unknown(false); + uncC->set_pixel_size(0); + uncC->set_row_align_size(0); + uncC->set_tile_align_size(0); + uncC->set_number_of_tile_columns(nTileColumns); + uncC->set_number_of_tile_rows(nTileRows); + } + else if (colourspace == heif_colorspace_monochrome) { + Box_cmpd::Component monoComponent = {component_type_monochrome}; + cmpd->add_component(monoComponent); + if (image->has_channel(heif_channel_Alpha)) { + Box_cmpd::Component alphaComponent = {component_type_alpha}; + cmpd->add_component(alphaComponent); + } + int bpp = image->get_bits_per_pixel(heif_channel_Y); + Box_uncC::Component component0 = {0, (uint8_t) (bpp), component_format_unsigned, 0}; + uncC->add_component(component0); + if (image->has_channel(heif_channel_Alpha)) { + bpp = image->get_bits_per_pixel(heif_channel_Alpha); + Box_uncC::Component component1 = {1, (uint8_t) (bpp), component_format_unsigned, 0}; + uncC->add_component(component1); + } + uncC->set_sampling_type(sampling_mode_no_subsampling); + uncC->set_interleave_type(interleave_mode_component); + uncC->set_block_size(0); + uncC->set_components_little_endian(false); + uncC->set_block_pad_lsb(false); + uncC->set_block_little_endian(false); + uncC->set_block_reversed(false); + uncC->set_pad_unknown(false); + uncC->set_pixel_size(0); + uncC->set_row_align_size(0); + uncC->set_tile_align_size(0); + uncC->set_number_of_tile_columns(nTileColumns); + uncC->set_number_of_tile_rows(nTileRows); + } + else { + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + "Unsupported colourspace"); + } + return Error::Ok; +} diff --git a/libheif/codecs/uncompressed/unc_codec.h b/libheif/codecs/uncompressed/unc_codec.h new file mode 100644 index 0000000000..b244c90eba --- /dev/null +++ b/libheif/codecs/uncompressed/unc_codec.h @@ -0,0 +1,78 @@ +/* + * HEIF codec. + * Copyright (c) 2023 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_UNC_CODEC_H +#define LIBHEIF_UNC_CODEC_H + +#include "pixelimage.h" +#include "file.h" +#include "context.h" + +#include +#include +#include +#include +#include + +class HeifContext; + + +bool isKnownUncompressedFrameConfigurationBoxProfile(const std::shared_ptr& uncC); + +Error fill_cmpd_and_uncC(std::shared_ptr& cmpd, + std::shared_ptr& uncC, + const std::shared_ptr& image, + const heif_unci_image_parameters* parameters); + +bool map_uncompressed_component_to_channel(const std::shared_ptr &cmpd, + const std::shared_ptr &uncC, + Box_uncC::Component component, + heif_channel *channel); + + +class UncompressedImageCodec +{ +public: + static Error decode_uncompressed_image(const HeifContext* context, + heif_item_id ID, + std::shared_ptr& img); + + static Error decode_uncompressed_image_tile(const HeifContext* context, + heif_item_id ID, + std::shared_ptr& img, + uint32_t tile_x0, uint32_t tile_y0); + + static Error get_heif_chroma_uncompressed(const std::shared_ptr& uncC, + const std::shared_ptr& cmpd, + heif_chroma* out_chroma, + heif_colorspace* out_colourspace); + + static Result> create_image(std::shared_ptr, + std::shared_ptr, + uint32_t width, + uint32_t height); + + static Error check_header_validity(std::optional>, + const std::shared_ptr&, + const std::shared_ptr&); +}; + +#endif //LIBHEIF_UNC_CODEC_H diff --git a/libheif/codecs/uncompressed/unc_dec.cc b/libheif/codecs/uncompressed/unc_dec.cc new file mode 100644 index 0000000000..d348b306f2 --- /dev/null +++ b/libheif/codecs/uncompressed/unc_dec.cc @@ -0,0 +1,151 @@ +/* + * HEIF codec. + * Copyright (c) 2024 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 "codecs/uncompressed/unc_dec.h" +#include "codecs/uncompressed/unc_codec.h" +#include "error.h" +#include "context.h" + +#include +#include + + +Result> Decoder_uncompressed::read_bitstream_configuration_data() const +{ + return std::vector{}; +} + + +int Decoder_uncompressed::get_luma_bits_per_pixel() const +{ + assert(m_uncC); + + if (!m_cmpd) { + if (isKnownUncompressedFrameConfigurationBoxProfile(m_uncC)) { + return 8; + } + else { + return -1; + } + } + + int luma_bits = 0; + int alternate_channel_bits = 0; + for (Box_uncC::Component component : m_uncC->get_components()) { + uint16_t component_index = component.component_index; + if (component_index >= m_cmpd->get_components().size()) { + return -1; + } + auto component_type = m_cmpd->get_components()[component_index].component_type; + switch (component_type) { + case component_type_monochrome: + case component_type_red: + case component_type_green: + case component_type_blue: + case component_type_filter_array: + alternate_channel_bits = std::max(alternate_channel_bits, (int) component.component_bit_depth); + break; + case component_type_Y: + luma_bits = std::max(luma_bits, (int) component.component_bit_depth); + break; + // TODO: there are other things we'll need to handle eventually, like palette. + } + } + if (luma_bits > 0) { + return luma_bits; + } + else if (alternate_channel_bits > 0) { + return alternate_channel_bits; + } + else { + return 8; + } +} + +int Decoder_uncompressed::get_chroma_bits_per_pixel() const +{ + if (m_uncC && m_uncC->get_version() == 1) { + // All of the version 1 cases are 8 bit + return 8; + } + + if (!m_uncC || !m_cmpd) { + return -1; + } + + int chroma_bits = 0; + int alternate_channel_bits = 0; + for (Box_uncC::Component component : m_uncC->get_components()) { + uint16_t component_index = component.component_index; + if (component_index >= m_cmpd->get_components().size()) { + return -1; + } + auto component_type = m_cmpd->get_components()[component_index].component_type; + switch (component_type) { + case component_type_monochrome: + case component_type_red: + case component_type_green: + case component_type_blue: + case component_type_filter_array: + alternate_channel_bits = std::max(alternate_channel_bits, (int) component.component_bit_depth); + break; + case component_type_Cb: + case component_type_Cr: + chroma_bits = std::max(chroma_bits, (int) component.component_bit_depth); + break; + // TODO: there are other things we'll need to handle eventually, like palette. + } + } + if (chroma_bits > 0) { + return chroma_bits; + } + else if (alternate_channel_bits > 0) { + return alternate_channel_bits; + } + else { + return 8; + } +} + + +Error Decoder_uncompressed::get_coded_image_colorspace(heif_colorspace* out_colorspace, heif_chroma* out_chroma) const +{ + if (m_uncC->get_version() == 1) { + // This is the shortform case, no cmpd box, and always some kind of RGB + *out_colorspace = heif_colorspace_RGB; + if (m_uncC->get_profile() == fourcc("rgb3")) { + *out_chroma = heif_chroma_interleaved_RGB; + } + else if ((m_uncC->get_profile() == fourcc("rgba")) || (m_uncC->get_profile() == fourcc("abgr"))) { + *out_chroma = heif_chroma_interleaved_RGBA; + } + + return Error::Ok; + } + else if (m_cmpd) { + UncompressedImageCodec::get_heif_chroma_uncompressed(m_uncC, m_cmpd, out_chroma, out_colorspace); + return Error::Ok; + } + else { + return {heif_error_Invalid_input, + heif_suberror_Unspecified, + "Missing 'cmpd' box."}; + } +} diff --git a/libheif/codecs/uncompressed/unc_dec.h b/libheif/codecs/uncompressed/unc_dec.h new file mode 100644 index 0000000000..2521df1493 --- /dev/null +++ b/libheif/codecs/uncompressed/unc_dec.h @@ -0,0 +1,57 @@ +/* + * HEIF codec. + * Copyright (c) 2024 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 HEIF_UNC_DEC_H +#define HEIF_UNC_DEC_H + +#include "libheif/heif.h" +#include "box.h" +#include "error.h" + +#include +#include +#include "codecs/decoder.h" + +class Box_uncC; +class Box_cmpd; + + +class Decoder_uncompressed : public Decoder +{ +public: + explicit Decoder_uncompressed(const std::shared_ptr& uncC, + const std::shared_ptr& cmpd) : m_uncC(uncC), m_cmpd(cmpd) {} + + heif_compression_format get_compression_format() const override { return heif_compression_uncompressed; } + + int get_luma_bits_per_pixel() const override; + + int get_chroma_bits_per_pixel() const override; + + Error get_coded_image_colorspace(heif_colorspace*, heif_chroma*) const override; + + Result> read_bitstream_configuration_data() const override; + +private: + const std::shared_ptr m_uncC; + const std::shared_ptr m_cmpd; +}; + +#endif diff --git a/libheif/codecs/uncompressed.h b/libheif/codecs/uncompressed/unc_types.h similarity index 97% rename from libheif/codecs/uncompressed.h rename to libheif/codecs/uncompressed/unc_types.h index a94587691e..1bcedf7316 100644 --- a/libheif/codecs/uncompressed.h +++ b/libheif/codecs/uncompressed/unc_types.h @@ -19,15 +19,15 @@ */ -#ifndef LIBHEIF_UNCOMPRESSED_H -#define LIBHEIF_UNCOMPRESSED_H +#ifndef LIBHEIF_UNC_TYPES_H +#define LIBHEIF_UNC_TYPES_H #include /** * Component type. * - * See ISO/IEC 23001-17 Table. + * See ISO/IEC 23001-17 Table 1. */ enum heif_uncompressed_component_type { @@ -282,4 +282,4 @@ enum heif_uncompressed_interleave_mode -#endif //LIBHEIF_UNCOMPRESSED_H +#endif //LIBHEIF_UNC_TYPES_H diff --git a/libheif/codecs/uncompressed_image.cc b/libheif/codecs/uncompressed_image.cc deleted file mode 100644 index 19005f03ae..0000000000 --- a/libheif/codecs/uncompressed_image.cc +++ /dev/null @@ -1,1325 +0,0 @@ -/* - * HEIF codec. - * Copyright (c) 2023 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 -#include -#include -#include -#include -#include - -#include "common_utils.h" -#include "error.h" -#include "libheif/heif.h" -#include "uncompressed.h" -#include "uncompressed_box.h" -#include "uncompressed_image.h" - -static bool isKnownUncompressedFrameConfigurationBoxProfile(const std::shared_ptr &uncC) -{ - return ((uncC != nullptr) && (uncC->get_version() == 1) && ((uncC->get_profile() == fourcc("rgb3")) || (uncC->get_profile() == fourcc("rgba")) || (uncC->get_profile() == fourcc("abgr")))); -} - -static Error uncompressed_image_type_is_supported(std::shared_ptr& uncC, std::shared_ptr& cmpd) -{ - if (isKnownUncompressedFrameConfigurationBoxProfile(uncC)) - { - return Error::Ok; - } - if (!cmpd) { - return Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_data_version, - "Missing required cmpd box (no match in uncC box) for uncompressed codec"); - } - - for (Box_uncC::Component component : uncC->get_components()) { - uint16_t component_index = component.component_index; - uint16_t component_type = cmpd->get_components()[component_index].component_type; - if ((component_type > 7) && (component_type != component_type_padded)) { - printf("unsupported component type: %d\n", component_type); - std::stringstream sstr; - sstr << "Uncompressed image with component_type " << ((int) component_type) << " is not implemented yet"; - return Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_data_version, - sstr.str()); - } - if ((component.component_bit_depth > 8) && (component.component_bit_depth != 16)) { - printf("unsupported component bit depth for index: %d, value: %d\n", component_index, component.component_bit_depth); - std::stringstream sstr; - sstr << "Uncompressed image with component_bit_depth " << ((int) component.component_bit_depth) << " is not implemented yet"; - return Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_data_version, - sstr.str()); - } - if (component.component_format != component_format_unsigned) { - printf("unsupported component format\n"); - std::stringstream sstr; - sstr << "Uncompressed image with component_format " << ((int) component.component_format) << " is not implemented yet"; - return Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_data_version, - sstr.str()); - } - if (component.component_align_size > 2) { - printf("unsupported component_align_size\n"); - std::stringstream sstr; - sstr << "Uncompressed image with component_align_size " << ((int) component.component_align_size) << " is not implemented yet"; - return Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_data_version, - sstr.str()); - } - } - if ((uncC->get_sampling_type() != sampling_mode_no_subsampling) - && (uncC->get_sampling_type() != sampling_mode_422) - && (uncC->get_sampling_type() != sampling_mode_420) - ) { - printf("bad sampling: %d\n", uncC->get_sampling_type()); - std::stringstream sstr; - sstr << "Uncompressed sampling_type of " << ((int) uncC->get_sampling_type()) << " is not implemented yet"; - return Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_data_version, - sstr.str()); - } - if ((uncC->get_interleave_type() != interleave_mode_component) - && (uncC->get_interleave_type() != interleave_mode_pixel) - && (uncC->get_interleave_type() != interleave_mode_mixed) - && (uncC->get_interleave_type() != interleave_mode_row) - && (uncC->get_interleave_type() != interleave_mode_tile_component) - ) { - printf("bad interleave: %d\n", uncC->get_interleave_type()); - std::stringstream sstr; - sstr << "Uncompressed interleave_type of " << ((int) uncC->get_interleave_type()) << " is not implemented yet"; - return Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_data_version, - sstr.str()); - } - // Validity checks per ISO/IEC 23001-17 Section 5.2.1.5.3 - if (uncC->get_sampling_type() == sampling_mode_422) { - // We check Y Cb and Cr appear in the chroma test - // TODO: error for tile width not multiple of 2 - if ((uncC->get_interleave_type() != interleave_mode_component) - && (uncC->get_interleave_type() != interleave_mode_mixed) - && (uncC->get_interleave_type() != interleave_mode_multi_y)) - { - std::stringstream sstr; - sstr << "YCbCr 4:2:2 subsampling is only valid with component, mixed or multi-Y interleave mode (ISO/IEC 23001-17 5.2.1.5.3)."; - return Error(heif_error_Invalid_input, - heif_suberror_Invalid_parameter_value, - sstr.str()); - } - if ((uncC->get_row_align_size() != 0) && (uncC->get_interleave_type() == interleave_mode_component)) { - if (uncC->get_row_align_size() % 2 != 0) { - std::stringstream sstr; - sstr << "YCbCr 4:2:2 subsampling with component interleave requires row_align_size to be a multiple of 2 (ISO/IEC 23001-17 5.2.1.5.3)."; - return Error(heif_error_Invalid_input, - heif_suberror_Invalid_parameter_value, - sstr.str()); - } - } - if (uncC->get_tile_align_size() != 0) { - if (uncC->get_tile_align_size() % 2 != 0) { - std::stringstream sstr; - sstr << "YCbCr 4:2:2 subsampling requires tile_align_size to be a multiple of 2 (ISO/IEC 23001-17 5.2.1.5.3)."; - return Error(heif_error_Invalid_input, - heif_suberror_Invalid_parameter_value, - sstr.str()); - } - } - } - // Validity checks per ISO/IEC 23001-17 Section 5.2.1.5.4 - if (uncC->get_sampling_type() == sampling_mode_422) { - // We check Y Cb and Cr appear in the chroma test - // TODO: error for tile width not multiple of 2 - if ((uncC->get_interleave_type() != interleave_mode_component) - && (uncC->get_interleave_type() != interleave_mode_mixed)) - { - std::stringstream sstr; - sstr << "YCbCr 4:2:0 subsampling is only valid with component or mixed interleave mode (ISO/IEC 23001-17 5.2.1.5.4)."; - return Error(heif_error_Invalid_input, - heif_suberror_Invalid_parameter_value, - sstr.str()); - } - if ((uncC->get_row_align_size() != 0) && (uncC->get_interleave_type() == interleave_mode_component)) { - if (uncC->get_row_align_size() % 2 != 0) { - std::stringstream sstr; - sstr << "YCbCr 4:2:2 subsampling with component interleave requires row_align_size to be a multiple of 2 (ISO/IEC 23001-17 5.2.1.5.4)."; - return Error(heif_error_Invalid_input, - heif_suberror_Invalid_parameter_value, - sstr.str()); - } - } - if (uncC->get_tile_align_size() != 0) { - if (uncC->get_tile_align_size() % 4 != 0) { - std::stringstream sstr; - sstr << "YCbCr 4:2:2 subsampling requires tile_align_size to be a multiple of 4 (ISO/IEC 23001-17 5.2.1.5.3)."; - return Error(heif_error_Invalid_input, - heif_suberror_Invalid_parameter_value, - sstr.str()); - } - } - } - if ((uncC->get_interleave_type() == interleave_mode_mixed) && (uncC->get_sampling_type() == sampling_mode_no_subsampling)) - { - std::stringstream sstr; - sstr << "Interleave interleave mode is not valid with subsampling mode (ISO/IEC 23001-17 5.2.1.6.4)."; - return Error(heif_error_Invalid_input, - heif_suberror_Invalid_parameter_value, - sstr.str()); - } - if ((uncC->get_interleave_type() == interleave_mode_multi_y) - && ((uncC->get_sampling_type() != sampling_mode_422) && (uncC->get_sampling_type() != sampling_mode_411))) - { - std::stringstream sstr; - sstr << "Multi-Y interleave mode is only valid with 4:2:2 and 4:1:1 subsampling modes (ISO/IEC 23001-17 5.2.1.6.7)."; - return Error(heif_error_Invalid_input, - heif_suberror_Invalid_parameter_value, - sstr.str()); - } - // TODO: throw error if mixed and Cb and Cr are not adjacent. - - if (uncC->get_block_size() != 0) { - printf("unsupported block size\n"); - std::stringstream sstr; - sstr << "Uncompressed block_size of " << ((int) uncC->get_block_size()) << " is not implemented yet"; - return Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_data_version, - sstr.str()); - } - if (uncC->is_components_little_endian()) { - printf("unsupported components LE\n"); - return Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_data_version, - "Uncompressed components_little_endian == 1 is not implemented yet"); - } - if (uncC->is_block_pad_lsb()) { - printf("unsupported block pad LSB\n"); - return Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_data_version, - "Uncompressed block_pad_lsb == 1 is not implemented yet"); - } - if (uncC->is_block_little_endian()) { - printf("unsupported block LE\n"); - return Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_data_version, - "Uncompressed block_little_endian == 1 is not implemented yet"); - } - if (uncC->is_block_reversed()) { - printf("unsupported block reversed\n"); - return Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_data_version, - "Uncompressed block_reversed == 1 is not implemented yet"); - } - if ((uncC->get_pixel_size() != 0) && ((uncC->get_interleave_type() != interleave_mode_pixel) && (uncC->get_interleave_type() != interleave_mode_multi_y))) { - std::stringstream sstr; - sstr << "Uncompressed pixel_size of " << ((int) uncC->get_pixel_size()) << " is only valid with interleave_type 1 or 5 (ISO/IEC 23001-17 5.2.1.7)"; - return Error(heif_error_Invalid_input, - heif_suberror_Invalid_parameter_value, - sstr.str()); - } - return Error::Ok; -} - - -static Error get_heif_chroma_uncompressed(std::shared_ptr& uncC, std::shared_ptr& cmpd, heif_chroma* out_chroma, heif_colorspace* out_colourspace) -{ - *out_chroma = heif_chroma_undefined; - *out_colourspace = heif_colorspace_undefined; - - if (isKnownUncompressedFrameConfigurationBoxProfile(uncC)) { - *out_chroma = heif_chroma_444; - *out_colourspace = heif_colorspace_RGB; - return Error::Ok; - } - - // each 1-bit represents an existing component in the image - uint16_t componentSet = 0; - - for (Box_uncC::Component component : uncC->get_components()) { - uint16_t component_index = component.component_index; - uint16_t component_type = cmpd->get_components()[component_index].component_type; - - if (component_type > component_type_max_valid) { - std::stringstream sstr; - sstr << "a component_type > " << component_type_max_valid << " is not supported"; - return { heif_error_Unsupported_feature, heif_suberror_Invalid_parameter_value, sstr.str()}; - } - if (component_type == component_type_padded) { - // not relevant for determining chroma - continue; - } - componentSet |= (1 << component_type); - } - - if (componentSet == ((1 << component_type_red) | (1 << component_type_green) | (1 << component_type_blue)) || - componentSet == ((1 << component_type_red) | (1 << component_type_green) | (1 << component_type_blue) | (1 << component_type_alpha))) { - *out_chroma = heif_chroma_444; - *out_colourspace = heif_colorspace_RGB; - } - - if (componentSet == ((1 << component_type_Y) | (1 << component_type_Cb) | (1 << component_type_Cr))) { - switch (uncC->get_sampling_type()) { - case sampling_mode_no_subsampling: - *out_chroma = heif_chroma_444; - break; - case sampling_mode_422: - *out_chroma = heif_chroma_422; - break; - case sampling_mode_420: - *out_chroma = heif_chroma_420; - break; - } - *out_colourspace = heif_colorspace_YCbCr; - } - - if (componentSet == ((1 << component_type_monochrome)) || componentSet == ((1 << component_type_monochrome) | (1 << component_type_alpha))) { - // mono or mono + alpha input, mono output. - *out_chroma = heif_chroma_monochrome; - *out_colourspace = heif_colorspace_monochrome; - } - - // TODO: more combinations - - if (*out_chroma == heif_chroma_undefined) { - printf("unknown chroma\n"); - return Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_data_version, - "Could not determine chroma"); - } - else if (*out_colourspace == heif_colorspace_undefined) { - printf("unknown colourspace\n"); - return Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_data_version, - "Could not determine colourspace"); - } - else { - return Error::Ok; - } -} - - -int UncompressedImageCodec::get_luma_bits_per_pixel_from_configuration_unci(const HeifFile& heif_file, heif_item_id imageID) -{ - auto ipco = heif_file.get_ipco_box(); - auto ipma = heif_file.get_ipma_box(); - - auto box1 = ipco->get_property_for_item_ID(imageID, ipma, fourcc("uncC")); - std::shared_ptr uncC_box = std::dynamic_pointer_cast(box1); - auto box2 = ipco->get_property_for_item_ID(imageID, ipma, fourcc("cmpd")); - std::shared_ptr cmpd_box = std::dynamic_pointer_cast(box2); - if (!uncC_box || !cmpd_box) { - return -1; - } - - int luma_bits = 0; - int alternate_channel_bits = 0; - for (Box_uncC::Component component : uncC_box->get_components()) { - uint16_t component_index = component.component_index; - if (component_index >= cmpd_box->get_components().size()) { - return -1; - } - auto component_type = cmpd_box->get_components()[component_index].component_type; - switch (component_type) { - case component_type_monochrome: - case component_type_red: - case component_type_green: - case component_type_blue: - alternate_channel_bits = std::max(alternate_channel_bits, (int)component.component_bit_depth); - break; - case component_type_Y: - luma_bits = std::max(luma_bits, (int)component.component_bit_depth); - break; - // TODO: there are other things we'll need to handle eventually, like palette. - } - } - if (luma_bits > 0) { - return luma_bits; - } - else if (alternate_channel_bits > 0) { - return alternate_channel_bits; - } - else { - return 8; - } -} - -static bool map_uncompressed_component_to_channel(const std::shared_ptr &cmpd, const std::shared_ptr &uncC, Box_uncC::Component component, heif_channel *channel) -{ - uint16_t component_index = component.component_index; - if (isKnownUncompressedFrameConfigurationBoxProfile(uncC)) { - if (uncC->get_profile() == fourcc("rgb3")) { - switch (component_index) { - case 0: - *channel = heif_channel_R; - return true; - case 1: - *channel = heif_channel_G; - return true; - case 2: - *channel = heif_channel_B; - return true; - } - } else if (uncC->get_profile() == fourcc("rgba")) { - switch (component_index) { - case 0: - *channel = heif_channel_Alpha; - return true; - case 1: - *channel = heif_channel_R; - return true; - case 2: - *channel = heif_channel_G; - return true; - case 3: - *channel = heif_channel_B; - return true; - } - } else if (uncC->get_profile() == fourcc("abgr")) { - switch (component_index) { - case 0: - *channel = heif_channel_Alpha; - return true; - case 1: - *channel = heif_channel_B; - return true; - case 2: - *channel = heif_channel_G; - return true; - case 3: - *channel = heif_channel_R; - return true; - } - } - } - uint16_t component_type = cmpd->get_components()[component_index].component_type; - - switch (component_type) { - case component_type_monochrome: - *channel = heif_channel_Y; - return true; - case component_type_Y: - *channel = heif_channel_Y; - return true; - case component_type_Cb: - *channel = heif_channel_Cb; - return true; - case component_type_Cr: - *channel = heif_channel_Cr; - return true; - case component_type_red: - *channel = heif_channel_R; - return true; - case component_type_green: - *channel = heif_channel_G; - return true; - case component_type_blue: - *channel = heif_channel_B; - return true; - case component_type_alpha: - *channel = heif_channel_Alpha; - return true; - case component_type_padded: - return false; - default: - printf("unmapped component_type: %d\n", component_type); - return false; - } -} - - class UncompressedBitReader : public BitReader - { - public: - UncompressedBitReader(const std::vector& data) : BitReader(data.data(), (int)data.size()) - {} - - void markPixelStart() { - m_pixelStartOffset = get_current_byte_index(); - } - - void markRowStart() { - m_rowStartOffset = get_current_byte_index(); - } - - void markTileStart() { - m_tileStartOffset = get_current_byte_index(); - } - - inline void handlePixelAlignment(uint32_t pixel_size) { - if (pixel_size != 0) { - uint32_t bytes_in_pixel = get_current_byte_index() - m_pixelStartOffset; - uint32_t padding = pixel_size - bytes_in_pixel; - skip_bytes(padding); - } - } - - void handleRowAlignment(uint32_t alignment) { - skip_to_byte_boundary(); - if (alignment != 0) { - uint32_t bytes_in_row = get_current_byte_index() - m_rowStartOffset; - uint32_t residual = bytes_in_row % alignment; - if (residual != 0) { - uint32_t padding = alignment - residual; - skip_bytes(padding); - } - } - } - - void handleTileAlignment(uint32_t alignment) { - if (alignment != 0) { - uint32_t bytes_in_tile = get_current_byte_index() - m_tileStartOffset; - uint32_t residual = bytes_in_tile % alignment; - if (residual != 0) { - uint32_t tile_padding = alignment - residual; - skip_bytes(tile_padding); - } - } - } - - private: - int m_pixelStartOffset; - int m_rowStartOffset; - int m_tileStartOffset; -}; - - -class AbstractDecoder -{ -public: - virtual Error decode(const std::vector& uncompressed_data, std::shared_ptr& img) = 0; - virtual ~AbstractDecoder() = default; -protected: - AbstractDecoder(uint32_t width, uint32_t height, const std::shared_ptr cmpd, const std::shared_ptr uncC): - m_width(width), - m_height(height), - m_cmpd(std::move(cmpd)), - m_uncC(std::move(uncC)) - { - m_tile_height = m_height / m_uncC->get_number_of_tile_rows(); - m_tile_width = m_width / m_uncC->get_number_of_tile_columns(); - } - - const uint32_t m_width; - const uint32_t m_height; - const std::shared_ptr m_cmpd; - const std::shared_ptr m_uncC; - // TODO: see if we can make this const - uint32_t m_tile_height; - uint32_t m_tile_width; - - class ChannelListEntry - { - public: - uint32_t get_bytes_per_tile() const - { - return bytes_per_tile_row_src * tile_height; - } - - inline uint64_t getDestinationRowOffset(uint32_t tile_row, uint32_t tile_y) const { - uint64_t dst_row_number = tile_row * tile_height + tile_y; - return dst_row_number * dst_plane_stride; - } - - heif_channel channel = heif_channel_Y; - uint8_t* dst_plane; - uint8_t* other_chroma_dst_plane; - int dst_plane_stride; - int other_chroma_dst_plane_stride; - uint32_t tile_width; - uint32_t tile_height; - uint32_t bytes_per_component_sample; - uint16_t bits_per_component_sample; - uint8_t component_alignment; - uint32_t bytes_per_tile_row_src; - bool use_channel; - }; - - std::vector channelList; - - void buildChannelList(std::shared_ptr& img) { - for (Box_uncC::Component component : m_uncC->get_components()) { - ChannelListEntry entry = buildChannelListEntry(component, img); - channelList.push_back(entry); - } - } - - protected: - void processComponentSample(UncompressedBitReader &srcBits, ChannelListEntry &entry, uint64_t dst_row_offset, uint32_t tile_column, uint32_t tile_x) { - uint64_t dst_col_number = tile_column * entry.tile_width + tile_x; - uint64_t dst_column_offset = dst_col_number * entry.bytes_per_component_sample; - int val = srcBits.get_bits(entry.bits_per_component_sample); - memcpy(entry.dst_plane + dst_row_offset + dst_column_offset, &val, entry.bytes_per_component_sample); - } - - // Handles the case where a row consists of a single component type - // Not valid for Pixel interleave - // Not valid for the Cb/Cr channels in Mixed Interleave - // Not valid for multi-Y pixel interleave - void processComponentRow(ChannelListEntry &entry, UncompressedBitReader &srcBits, uint64_t dst_row_offset, uint32_t tile_column) - { - for (uint32_t tile_x = 0; tile_x < entry.tile_width; tile_x++) { - if (entry.component_alignment != 0) { - srcBits.skip_to_byte_boundary(); - int numPadBits = (entry.component_alignment * 8) - entry.bits_per_component_sample; - srcBits.skip_bits(numPadBits); - } - processComponentSample(srcBits, entry, dst_row_offset, tile_column, tile_x); - } - srcBits.skip_to_byte_boundary(); - } - - private: - ChannelListEntry buildChannelListEntry(Box_uncC::Component component, std::shared_ptr &img) { - ChannelListEntry entry; - entry.use_channel = map_uncompressed_component_to_channel(m_cmpd, m_uncC, component, &(entry.channel)); - entry.dst_plane = img->get_plane(entry.channel, &(entry.dst_plane_stride)); - entry.tile_width = m_tile_width; - entry.tile_height = m_tile_height; - if ((entry.channel == heif_channel_Cb) || (entry.channel == heif_channel_Cr)) { - if (m_uncC->get_sampling_type() == sampling_mode_422) { - entry.tile_width /= 2; - } else if (m_uncC->get_sampling_type() == sampling_mode_420) { - entry.tile_width /= 2; - entry.tile_height /= 2; - } - if (entry.channel == heif_channel_Cb) { - entry.other_chroma_dst_plane = img->get_plane(heif_channel_Cr, &(entry.other_chroma_dst_plane_stride)); - } else if (entry.channel == heif_channel_Cr) { - entry.other_chroma_dst_plane = img->get_plane(heif_channel_Cb, &(entry.other_chroma_dst_plane_stride)); - } - } - entry.bits_per_component_sample = component.component_bit_depth; - entry.component_alignment = component.component_align_size; - entry.bytes_per_component_sample = (component.component_bit_depth + 7) / 8; - entry.bytes_per_tile_row_src = entry.tile_width * entry.bytes_per_component_sample; - return entry; - } -}; - - -class ComponentInterleaveDecoder : public AbstractDecoder -{ -public: - ComponentInterleaveDecoder(uint32_t width, uint32_t height, std::shared_ptr cmpd, std::shared_ptr uncC): - AbstractDecoder(width, height, std::move(cmpd), std::move(uncC)) - {} - - Error decode(const std::vector& uncompressed_data, std::shared_ptr& img) override { - UncompressedBitReader srcBits(uncompressed_data); - - buildChannelList(img); - - for (uint32_t tile_row = 0; tile_row < m_uncC->get_number_of_tile_rows(); tile_row++) { - for (uint32_t tile_column = 0; tile_column < m_uncC->get_number_of_tile_columns(); tile_column++) { - srcBits.markTileStart(); - processTile(srcBits, tile_row, tile_column); - srcBits.handleTileAlignment(m_uncC->get_tile_align_size()); - } - } - - return Error::Ok; - } - - void processTile(UncompressedBitReader &srcBits, uint32_t tile_row, uint32_t tile_column) { - for (ChannelListEntry &entry : channelList) { - for (uint32_t tile_y = 0; tile_y < entry.tile_height; tile_y++) { - srcBits.markRowStart(); - if (entry.use_channel) { - uint64_t dst_row_offset = entry.getDestinationRowOffset(tile_row, tile_y); - processComponentRow(entry, srcBits, dst_row_offset, tile_column); - } else { - srcBits.skip_bytes(entry.bytes_per_tile_row_src); - } - srcBits.handleRowAlignment(m_uncC->get_row_align_size()); - } - } - } -}; - -class PixelInterleaveDecoder : public AbstractDecoder -{ -public: - PixelInterleaveDecoder(uint32_t width, uint32_t height, std::shared_ptr cmpd, std::shared_ptr uncC): - AbstractDecoder(width, height, std::move(cmpd), std::move(uncC)) - {} - - Error decode(const std::vector& uncompressed_data, std::shared_ptr& img) override { - UncompressedBitReader srcBits(uncompressed_data); - - buildChannelList(img); - - for (uint32_t tile_row = 0; tile_row < m_uncC->get_number_of_tile_rows(); tile_row++) { - for (uint32_t tile_column = 0; tile_column < m_uncC->get_number_of_tile_columns(); tile_column++) { - srcBits.markTileStart(); - processTile(srcBits, tile_row, tile_column); - srcBits.handleTileAlignment(m_uncC->get_tile_align_size()); - } - } - - return Error::Ok; - } - - void processTile(UncompressedBitReader &srcBits, uint32_t tile_row, uint32_t tile_column) { - for (uint32_t tile_y = 0; tile_y < m_tile_height; tile_y++) { - srcBits.markRowStart(); - for (uint32_t tile_x = 0; tile_x < m_tile_width; tile_x++) { - srcBits.markPixelStart(); - for (ChannelListEntry &entry : channelList) { - if (entry.use_channel) { - uint64_t dst_row_offset = entry.getDestinationRowOffset(tile_row, tile_y); - if (entry.component_alignment != 0) { - srcBits.skip_to_byte_boundary(); - int numPadBits = (entry.component_alignment * 8) - entry.bits_per_component_sample; - srcBits.skip_bits(numPadBits); - } - processComponentSample(srcBits, entry, dst_row_offset, tile_column, tile_x); - } else { - srcBits.skip_bytes(entry.bytes_per_component_sample); - } - } - srcBits.handlePixelAlignment(m_uncC->get_pixel_size()); - } - srcBits.handleRowAlignment(m_uncC->get_row_align_size()); - } - } -}; - -class MixedInterleaveDecoder : public AbstractDecoder -{ -public: - MixedInterleaveDecoder(uint32_t width, uint32_t height, std::shared_ptr cmpd, std::shared_ptr uncC): - AbstractDecoder(width, height, std::move(cmpd), std::move(uncC)) - {} - - Error decode(const std::vector& uncompressed_data, std::shared_ptr& img) override { - UncompressedBitReader srcBits(uncompressed_data); - - buildChannelList(img); - - for (uint32_t tile_row = 0; tile_row < m_uncC->get_number_of_tile_rows(); tile_row++) { - for (uint32_t tile_column = 0; tile_column < m_uncC->get_number_of_tile_columns(); tile_column++) { - srcBits.markTileStart(); - processTile(srcBits, tile_row, tile_column); - srcBits.handleTileAlignment(m_uncC->get_tile_align_size()); - } - } - - return Error::Ok; - } - - void processTile(UncompressedBitReader &srcBits, uint32_t tile_row, uint32_t tile_column) { - bool haveProcessedChromaForThisTile = false; - for (ChannelListEntry &entry : channelList) { - if (entry.use_channel) { - if ((entry.channel == heif_channel_Cb) || (entry.channel == heif_channel_Cr)) { - if (!haveProcessedChromaForThisTile) { - for (uint32_t tile_y = 0; tile_y < entry.tile_height; tile_y++) { - // TODO: row padding - uint64_t dst_row_number = tile_row * entry.tile_width + tile_y; - uint64_t dst_row_offset = dst_row_number * entry.dst_plane_stride; - for (uint32_t tile_x = 0; tile_x < entry.tile_width; tile_x++) { - uint64_t dst_column_number = tile_column * entry.tile_width + tile_x; - uint64_t dst_column_offset = dst_column_number * entry.bytes_per_component_sample; - int val = srcBits.get_bits(entry.bytes_per_component_sample * 8); - memcpy(entry.dst_plane + dst_row_offset + dst_column_offset, &val, entry.bytes_per_component_sample); - val = srcBits.get_bits(entry.bytes_per_component_sample * 8); - memcpy(entry.other_chroma_dst_plane + dst_row_offset + dst_column_offset, &val, entry.bytes_per_component_sample); - } - haveProcessedChromaForThisTile = true; - } - } - } else { - for (uint32_t tile_y = 0; tile_y < entry.tile_height; tile_y++) { - uint64_t dst_row_offset = entry.getDestinationRowOffset(tile_row, tile_y); - processComponentRow(entry, srcBits, dst_row_offset, tile_column); - } - } - } else { - // skip over the data we are not using - srcBits.skip_bytes(entry.get_bytes_per_tile()); - continue; - } - } - } -}; - -class RowInterleaveDecoder : public AbstractDecoder -{ -public: - RowInterleaveDecoder(uint32_t width, uint32_t height, std::shared_ptr cmpd, std::shared_ptr uncC): - AbstractDecoder(width, height, std::move(cmpd), std::move(uncC)) - {} - - Error decode(const std::vector& uncompressed_data, std::shared_ptr& img) override { - UncompressedBitReader srcBits(uncompressed_data); - buildChannelList(img); - for (uint32_t tile_row = 0; tile_row < m_uncC->get_number_of_tile_rows(); tile_row++) { - for (uint32_t tile_column = 0; tile_column < m_uncC->get_number_of_tile_columns(); tile_column++) { - srcBits.markTileStart(); - processTile(srcBits, tile_row, tile_column); - srcBits.handleTileAlignment(m_uncC->get_tile_align_size()); - } - } - return Error::Ok; - } - -private: - void processTile(UncompressedBitReader &srcBits, uint32_t tile_row, uint32_t tile_column) { - for (uint32_t tile_y = 0; tile_y < m_tile_height; tile_y++) { - for (ChannelListEntry &entry : channelList) { - srcBits.markRowStart(); - if (entry.use_channel) { - uint64_t dst_row_offset = entry.getDestinationRowOffset(tile_row, tile_y); - processComponentRow(entry, srcBits, dst_row_offset, tile_column); - } else { - srcBits.skip_bytes(entry.bytes_per_tile_row_src); - } - srcBits.handleRowAlignment(m_uncC->get_row_align_size()); - } - } - } -}; - - -class TileComponentInterleaveDecoder : public AbstractDecoder -{ -public: - TileComponentInterleaveDecoder(uint32_t width, uint32_t height, std::shared_ptr cmpd, std::shared_ptr uncC): - AbstractDecoder(width, height, std::move(cmpd), std::move(uncC)) - {} - - Error decode(const std::vector& uncompressed_data, std::shared_ptr& img) override { - UncompressedBitReader srcBits(uncompressed_data); - - buildChannelList(img); - - for (ChannelListEntry &entry : channelList) { - if (!entry.use_channel) { - uint64_t bytes_per_component = entry.get_bytes_per_tile() * m_uncC->get_number_of_tile_columns() * m_uncC->get_number_of_tile_rows(); - srcBits.skip_bytes((int)bytes_per_component); - continue; - } - for (uint32_t tile_row = 0; tile_row < m_uncC->get_number_of_tile_rows(); tile_row++) { - for (uint32_t tile_column = 0; tile_column < m_uncC->get_number_of_tile_columns(); tile_column++) { - srcBits.markTileStart(); - for (uint32_t tile_y = 0; tile_y < entry.tile_height; tile_y++) { - srcBits.markRowStart(); - uint64_t dst_row_offset = entry.getDestinationRowOffset(tile_row, tile_y); - processComponentRow(entry, srcBits, dst_row_offset, tile_column); - srcBits.handleRowAlignment(m_uncC->get_row_align_size()); - } - srcBits.handleTileAlignment(m_uncC->get_tile_align_size()); - } - } - } - - return Error::Ok; - } -}; - -static AbstractDecoder* makeDecoder(uint32_t width, uint32_t height, const std::shared_ptr& cmpd, const std::shared_ptr& uncC) -{ - if (uncC->get_interleave_type() == interleave_mode_component) { - return new ComponentInterleaveDecoder(width, height, cmpd, uncC); - } else if (uncC->get_interleave_type() == interleave_mode_pixel) { - return new PixelInterleaveDecoder(width, height, cmpd, uncC); - } else if (uncC->get_interleave_type() == interleave_mode_mixed) { - return new MixedInterleaveDecoder(width, height, cmpd, uncC); - } else if (uncC->get_interleave_type() == interleave_mode_row) { - return new RowInterleaveDecoder(width, height, cmpd, uncC); - } else if (uncC->get_interleave_type() == interleave_mode_tile_component) { - return new TileComponentInterleaveDecoder(width, height, cmpd, uncC); - } else { - return nullptr; - } -} - -Error UncompressedImageCodec::decode_uncompressed_image(const std::shared_ptr& heif_file, - heif_item_id ID, - std::shared_ptr& img, - uint32_t maximum_image_width_limit, - uint32_t maximum_image_height_limit, - const std::vector& uncompressed_data) -{ - if (uncompressed_data.empty()) { - return {heif_error_Invalid_input, - heif_suberror_Unspecified, - "Uncompressed image data is empty"}; - } - - // Get the properties for this item - // We need: ispe, cmpd, uncC - std::vector> item_properties; - Error error = heif_file->get_properties(ID, item_properties); - if (error) { - printf("failed to get properties\n"); - return error; - } - - uint32_t width = 0; - uint32_t height = 0; - bool found_ispe = false; - std::shared_ptr cmpd; - std::shared_ptr uncC; - for (const auto& prop : item_properties) { - auto ispe = std::dynamic_pointer_cast(prop); - if (ispe) { - width = ispe->get_width(); - height = ispe->get_height(); - - if (width >= maximum_image_width_limit || height >= maximum_image_height_limit) { - std::stringstream sstr; - sstr << "Image size " << width << "x" << height << " exceeds the maximum image size " - << maximum_image_width_limit << "x" << maximum_image_height_limit << "\n"; - printf("way too big\n"); - return Error(heif_error_Memory_allocation_error, - heif_suberror_Security_limit_exceeded, - sstr.str()); - } - found_ispe = true; - } - - auto maybe_cmpd = std::dynamic_pointer_cast(prop); - if (maybe_cmpd) { - cmpd = maybe_cmpd; - } - - auto maybe_uncC = std::dynamic_pointer_cast(prop); - if (maybe_uncC) { - uncC = maybe_uncC; - } - } - - - // if we miss a required box, show error - if (!found_ispe) { - return Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_data_version, - "Missing required ispe box for uncompressed codec"); - } - if (!uncC) { - return Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_data_version, - "Missing required uncC box for uncompressed codec"); - } - if (!cmpd && (uncC->get_version() !=1)) { - return Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_data_version, - "Missing required cmpd or uncC version 1 box for uncompressed codec"); -} - - // check if we support the type of image - - error = uncompressed_image_type_is_supported(uncC, cmpd); - if (error) { - printf("unsupported image type\n"); - return error; - } - - img = std::make_shared(); - heif_chroma chroma; - heif_colorspace colourspace; - error = get_heif_chroma_uncompressed(uncC, cmpd, &chroma, &colourspace); - if (error) { - printf("failed to get chroma uncompressed\n"); - return error; - } - img->create(width, height, - colourspace, - chroma); - - for (Box_uncC::Component component : uncC->get_components()) { - heif_channel channel; - if (map_uncompressed_component_to_channel(cmpd, uncC, component, &channel)) { - if ((channel == heif_channel_Cb) || (channel == heif_channel_Cr)) { - img->add_plane(channel, (width / chroma_h_subsampling(chroma)), (height / chroma_v_subsampling(chroma)), component.component_bit_depth); - } else { - img->add_plane(channel, width, height, component.component_bit_depth); - } - } - } - - AbstractDecoder *decoder = makeDecoder(width, height, cmpd, uncC); - if (decoder != nullptr) { - Error result = decoder->decode(uncompressed_data, img); - delete decoder; - return result; - } else { - printf("bad interleave mode - we should have detected this earlier: %d\n", uncC->get_interleave_type()); - std::stringstream sstr; - sstr << "Uncompressed interleave_type of " << ((int) uncC->get_interleave_type()) << " is not implemented yet"; - return Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_data_version, - sstr.str()); - } -} - -Error fill_cmpd_and_uncC(std::shared_ptr& cmpd, std::shared_ptr& uncC, const std::shared_ptr& image) -{ - const heif_colorspace colourspace = image->get_colorspace(); - if (colourspace == heif_colorspace_YCbCr) { - if (!(image->has_channel(heif_channel_Y) && image->has_channel(heif_channel_Cb) && image->has_channel(heif_channel_Cr))) - { - return Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_data_version, - "Invalid colourspace / channel combination - YCbCr"); - } - Box_cmpd::Component yComponent = {component_type_Y}; - cmpd->add_component(yComponent); - Box_cmpd::Component cbComponent = {component_type_Cb}; - cmpd->add_component(cbComponent); - Box_cmpd::Component crComponent = {component_type_Cr}; - cmpd->add_component(crComponent); - uint8_t bpp_y = image->get_bits_per_pixel(heif_channel_Y); - Box_uncC::Component component0 = {0, bpp_y, component_format_unsigned, 0}; - uncC->add_component(component0); - uint8_t bpp_cb = image->get_bits_per_pixel(heif_channel_Cb); - Box_uncC::Component component1 = {1, bpp_cb, component_format_unsigned, 0}; - uncC->add_component(component1); - uint8_t bpp_cr = image->get_bits_per_pixel(heif_channel_Cr); - Box_uncC::Component component2 = {2, bpp_cr, component_format_unsigned, 0}; - uncC->add_component(component2); - if (image->get_chroma_format() == heif_chroma_444) - { - uncC->set_sampling_type(sampling_mode_no_subsampling); - } - else if (image->get_chroma_format() == heif_chroma_422) - { - uncC->set_sampling_type(sampling_mode_422); - } - else if (image->get_chroma_format() == heif_chroma_420) - { - uncC->set_sampling_type(sampling_mode_420); - } - else - { - return Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_data_version, - "Unsupported YCbCr sub-sampling type"); - } - uncC->set_interleave_type(interleave_mode_component); - uncC->set_block_size(0); - uncC->set_components_little_endian(false); - uncC->set_block_pad_lsb(false); - uncC->set_block_little_endian(false); - uncC->set_block_reversed(false); - uncC->set_pad_unknown(false); - uncC->set_pixel_size(0); - uncC->set_row_align_size(0); - uncC->set_tile_align_size(0); - uncC->set_number_of_tile_columns(1); - uncC->set_number_of_tile_rows(1); - } - else if (colourspace == heif_colorspace_RGB) - { - if (!((image->get_chroma_format() == heif_chroma_444) || - (image->get_chroma_format() == heif_chroma_interleaved_RGB) || - (image->get_chroma_format() == heif_chroma_interleaved_RGBA) || - (image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_BE) || - (image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_LE) || - (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_BE) || - (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE))) { - return Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_data_version, - "Unsupported colourspace / chroma combination - RGB"); - } - Box_cmpd::Component rComponent = {component_type_red}; - cmpd->add_component(rComponent); - Box_cmpd::Component gComponent = {component_type_green}; - cmpd->add_component(gComponent); - Box_cmpd::Component bComponent = {component_type_blue}; - cmpd->add_component(bComponent); - if ((image->get_chroma_format() == heif_chroma_interleaved_RGBA) || - (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_BE) || - (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE) || - (image->has_channel(heif_channel_Alpha))) - { - Box_cmpd::Component alphaComponent = {component_type_alpha}; - cmpd->add_component(alphaComponent); - } - if ((image->get_chroma_format() == heif_chroma_interleaved_RGB) || - (image->get_chroma_format() == heif_chroma_interleaved_RGBA) || - (image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_BE) || - (image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_LE) || - (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_BE) || - (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE)) - { - uncC->set_interleave_type(interleave_mode_pixel); - int bpp = image->get_bits_per_pixel(heif_channel_interleaved); - uint8_t component_align = 1; - if (bpp == 8) - { - component_align = 0; - } - else if (bpp > 8) - { - component_align = 2; - } - Box_uncC::Component component0 = {0, (uint8_t)(bpp), component_format_unsigned, component_align}; - uncC->add_component(component0); - Box_uncC::Component component1 = {1, (uint8_t)(bpp), component_format_unsigned, component_align}; - uncC->add_component(component1); - Box_uncC::Component component2 = {2, (uint8_t)(bpp), component_format_unsigned, component_align}; - uncC->add_component(component2); - if ((image->get_chroma_format() == heif_chroma_interleaved_RGBA) || - (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_BE) || - (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE)) - { - Box_uncC::Component component3 = { - 3, (uint8_t)(bpp), component_format_unsigned, component_align}; - uncC->add_component(component3); - } - } else { - uncC->set_interleave_type(interleave_mode_component); - int bpp_red = image->get_bits_per_pixel(heif_channel_R); - Box_uncC::Component component0 = {0, (uint8_t)(bpp_red), component_format_unsigned, 0}; - uncC->add_component(component0); - int bpp_green = image->get_bits_per_pixel(heif_channel_G); - Box_uncC::Component component1 = {1, (uint8_t)(bpp_green), component_format_unsigned, 0}; - uncC->add_component(component1); - int bpp_blue = image->get_bits_per_pixel(heif_channel_B); - Box_uncC::Component component2 = {2, (uint8_t)(bpp_blue), component_format_unsigned, 0}; - uncC->add_component(component2); - if(image->has_channel(heif_channel_Alpha)) - { - int bpp_alpha = image->get_bits_per_pixel(heif_channel_Alpha); - Box_uncC::Component component3 = {3, (uint8_t)(bpp_alpha), component_format_unsigned, 0}; - uncC->add_component(component3); - } - } - uncC->set_sampling_type(sampling_mode_no_subsampling); - uncC->set_block_size(0); - if ((image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_LE) || - (image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE)) - { - uncC->set_components_little_endian(true); - } else { - uncC->set_components_little_endian(false); - } - uncC->set_block_pad_lsb(false); - uncC->set_block_little_endian(false); - uncC->set_block_reversed(false); - uncC->set_pad_unknown(false); - uncC->set_pixel_size(0); - uncC->set_row_align_size(0); - uncC->set_tile_align_size(0); - uncC->set_number_of_tile_columns(1); - uncC->set_number_of_tile_rows(1); - } - else if (colourspace == heif_colorspace_monochrome) - { - Box_cmpd::Component monoComponent = {component_type_monochrome}; - cmpd->add_component(monoComponent); - if (image->has_channel(heif_channel_Alpha)) - { - Box_cmpd::Component alphaComponent = {component_type_alpha}; - cmpd->add_component(alphaComponent); - } - int bpp = image->get_bits_per_pixel(heif_channel_Y); - Box_uncC::Component component0 = {0, (uint8_t)(bpp), component_format_unsigned, 0}; - uncC->add_component(component0); - if (image->has_channel(heif_channel_Alpha)) - { - bpp = image->get_bits_per_pixel(heif_channel_Alpha); - Box_uncC::Component component1 = {1, (uint8_t)(bpp), component_format_unsigned, 0}; - uncC->add_component(component1); - } - uncC->set_sampling_type(sampling_mode_no_subsampling); - uncC->set_interleave_type(interleave_mode_component); - uncC->set_block_size(0); - uncC->set_components_little_endian(false); - uncC->set_block_pad_lsb(false); - uncC->set_block_little_endian(false); - uncC->set_block_reversed(false); - uncC->set_pad_unknown(false); - uncC->set_pixel_size(0); - uncC->set_row_align_size(0); - uncC->set_tile_align_size(0); - uncC->set_number_of_tile_columns(1); - uncC->set_number_of_tile_rows(1); - } - else - { - return Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_data_version, - "Unsupported colourspace"); - } - return Error::Ok; -} - - -static void maybe_make_minimised_uncC(std::shared_ptr& uncC, const std::shared_ptr& image) -{ - uncC->set_version(0); - if (image->get_colorspace() != heif_colorspace_RGB) { - return; - } - if (!((image->get_chroma_format() == heif_chroma_interleaved_RGB) || (image->get_chroma_format() == heif_chroma_interleaved_RGBA))) { - return; - } - if (image->get_bits_per_pixel(heif_channel_interleaved) != 8) { - return; - } - if (image->get_chroma_format() == heif_chroma_interleaved_RGBA) { - uncC->set_profile(fourcc_to_uint32("rgba")); - } else { - uncC->set_profile(fourcc_to_uint32("rgb3")); - } - uncC->set_version(1); -} - -Error UncompressedImageCodec::encode_uncompressed_image(const std::shared_ptr& heif_file, - const std::shared_ptr& src_image, - void* encoder_struct, - const struct heif_encoding_options& options, - std::shared_ptr& out_image) -{ - std::shared_ptr uncC = std::make_shared(); - if (options.prefer_uncC_short_form) { - maybe_make_minimised_uncC(uncC, src_image); - } - if (uncC->get_version() == 1) { - heif_file->add_property(out_image->get_id(), uncC, true); - } else { - std::shared_ptr cmpd = std::make_shared(); - - Error error = fill_cmpd_and_uncC(cmpd, uncC, src_image); - if (error) { - return error; - } - heif_file->add_property(out_image->get_id(), cmpd, true); - heif_file->add_property(out_image->get_id(), uncC, true); - } - std::vector data; - if (src_image->get_colorspace() == heif_colorspace_YCbCr) - { - uint64_t offset = 0; - for (heif_channel channel : {heif_channel_Y, heif_channel_Cb, heif_channel_Cr}) - { - int src_stride; - uint8_t* src_data = src_image->get_plane(channel, &src_stride); - uint64_t out_size = src_image->get_height() * src_image->get_width(); - data.resize(data.size() + out_size); - for (int y = 0; y < src_image->get_height(); y++) { - memcpy(data.data() + offset + y * src_image->get_width(), src_data + src_stride * y, src_image->get_width()); - } - offset += out_size; - } - heif_file->append_iloc_data(out_image->get_id(), data, 0); - } - else if (src_image->get_colorspace() == heif_colorspace_RGB) - { - if (src_image->get_chroma_format() == heif_chroma_444) - { - uint64_t offset = 0; - std::vector channels = {heif_channel_R, heif_channel_G, heif_channel_B}; - if (src_image->has_channel(heif_channel_Alpha)) - { - channels.push_back(heif_channel_Alpha); - } - for (heif_channel channel : channels) - { - int src_stride; - uint8_t* src_data = src_image->get_plane(channel, &src_stride); - uint64_t out_size = src_image->get_height() * src_stride; - data.resize(data.size() + out_size); - memcpy(data.data() + offset, src_data, out_size); - offset += out_size; - } - heif_file->append_iloc_data(out_image->get_id(), data, 0); - } - else if ((src_image->get_chroma_format() == heif_chroma_interleaved_RGB) || - (src_image->get_chroma_format() == heif_chroma_interleaved_RGBA) || - (src_image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_BE) || - (src_image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_LE) || - (src_image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_BE) || - (src_image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE)) - { - int bytes_per_pixel = 0; - switch (src_image->get_chroma_format()) { - case heif_chroma_interleaved_RGB: - bytes_per_pixel=3; - break; - case heif_chroma_interleaved_RGBA: - bytes_per_pixel=4; - break; - case heif_chroma_interleaved_RRGGBB_BE: - case heif_chroma_interleaved_RRGGBB_LE: - bytes_per_pixel=6; - break; - case heif_chroma_interleaved_RRGGBBAA_BE: - case heif_chroma_interleaved_RRGGBBAA_LE: - bytes_per_pixel=8; - break; - default: - assert(false); - } - - int src_stride; - uint8_t* src_data = src_image->get_plane(heif_channel_interleaved, &src_stride); - uint64_t out_size = src_image->get_height() * src_image->get_width() * bytes_per_pixel; - data.resize(out_size); - for (int y = 0; y < src_image->get_height(); y++) { - memcpy(data.data() + y * src_image->get_width() * bytes_per_pixel, src_data + src_stride * y, src_image->get_width() * bytes_per_pixel); - } - heif_file->append_iloc_data(out_image->get_id(), data, 0); - } - else - { - return Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_data_version, - "Unsupported RGB chroma"); - } - } - else if (src_image->get_colorspace() == heif_colorspace_monochrome) - { - uint64_t offset = 0; - std::vector channels; - if (src_image->has_channel(heif_channel_Alpha)) - { - channels = {heif_channel_Y, heif_channel_Alpha}; - } - else - { - channels = {heif_channel_Y}; - } - for (heif_channel channel : channels) - { - int src_stride; - uint8_t* src_data = src_image->get_plane(channel, &src_stride); - uint64_t out_size = src_image->get_height() * src_stride; - data.resize(data.size() + out_size); - memcpy(data.data() + offset, src_data, out_size); - offset += out_size; - } - heif_file->append_iloc_data(out_image->get_id(), data, 0); - } - else - { - return Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_data_version, - "Unsupported colourspace"); - } - // We need to ensure ispe is essential for the uncompressed case - std::shared_ptr ispe = std::make_shared(); - ispe->set_size(src_image->get_width(), src_image->get_height()); - heif_file->add_property(out_image->get_id(), ispe, true); - - return Error::Ok; -} diff --git a/libheif/codecs/uncompressed_image.h b/libheif/codecs/uncompressed_image.h deleted file mode 100644 index 742f9a76f8..0000000000 --- a/libheif/codecs/uncompressed_image.h +++ /dev/null @@ -1,54 +0,0 @@ -/* - * HEIF codec. - * Copyright (c) 2023 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_UNCOMPRESSED_IMAGE_H -#define LIBHEIF_UNCOMPRESSED_IMAGE_H - -#include "pixelimage.h" -#include "file.h" -#include "context.h" - -#include -#include -#include -#include - - -class UncompressedImageCodec -{ -public: - static int get_luma_bits_per_pixel_from_configuration_unci(const HeifFile& heif_file, heif_item_id imageID); - - static Error decode_uncompressed_image(const std::shared_ptr& heif_file, - heif_item_id ID, - std::shared_ptr& img, - uint32_t maximum_image_width_limit, - uint32_t maximum_image_height_limit, - const std::vector& uncompressed_data); - - static Error encode_uncompressed_image(const std::shared_ptr& heif_file, - const std::shared_ptr& src_image, - void* encoder_struct, - const struct heif_encoding_options& options, - std::shared_ptr& out_image); -}; - -#endif //LIBHEIF_UNCOMPRESSED_IMAGE_H diff --git a/libheif/codecs/vvc.cc b/libheif/codecs/vvc.cc deleted file mode 100644 index 2fb04f835a..0000000000 --- a/libheif/codecs/vvc.cc +++ /dev/null @@ -1,389 +0,0 @@ -/* - * HEIF VVC codec. - * Copyright (c) 2023 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 "vvc.h" -#include -#include -#include -#include -#include - -Error Box_vvcC::parse(BitstreamRange& range) -{ - //parse_full_box_header(range); - - uint8_t byte; - - auto& c = m_configuration; // abbreviation - - c.configurationVersion = range.read8(); - c.avgFrameRate_times_256 = range.read16(); - - //printf("version: %d\n", c.configurationVersion); - - byte = range.read8(); - c.constantFrameRate = (byte & 0xc0) >> 6; - c.numTemporalLayers = (byte & 0x38) >> 3; - c.lengthSize = uint8_t(((byte & 0x06) >> 1) + 1); - c.ptl_present_flag = (byte & 0x01); - // assert(c.ptl_present_flag == false); // TODO (removed the assert since it will trigger the fuzzers) - - byte = range.read8(); - c.chroma_format_present_flag = (byte & 0x80); - c.chroma_format_idc = (byte & 0x60) >> 5; - - c.bit_depth_present_flag = (byte & 0x10); - c.bit_depth = uint8_t(((byte & 0x0e) >> 1) + 8); - - int nArrays = range.read8(); - - for (int i = 0; i < nArrays && !range.error(); i++) { - byte = range.read8(); - - NalArray array; - - array.m_array_completeness = (byte >> 6) & 1; - array.m_NAL_unit_type = (byte & 0x3F); - - int nUnits = range.read16(); - for (int u = 0; u < nUnits && !range.error(); u++) { - - std::vector nal_unit; - int size = range.read16(); - if (!size) { - // Ignore empty NAL units. - continue; - } - - if (range.prepare_read(size)) { - nal_unit.resize(size); - bool success = range.get_istream()->read((char*) nal_unit.data(), size); - if (!success) { - return Error{heif_error_Invalid_input, heif_suberror_End_of_data, "error while reading hvcC box"}; - } - } - - array.m_nal_units.push_back(std::move(nal_unit)); - } - - m_nal_array.push_back(std::move(array)); - } - -#if 0 - const int64_t configOBUs_bytes = range.get_remaining_bytes(); - m_config_OBUs.resize(configOBUs_bytes); - - if (!range.read(m_config_OBUs.data(), configOBUs_bytes)) { - // error - } -#endif - - return range.get_error(); -} - - -void Box_vvcC::append_nal_data(const std::vector& nal) -{ - NalArray array; - array.m_array_completeness = 0; - array.m_NAL_unit_type = uint8_t(nal[0] >> 1); - array.m_nal_units.push_back(nal); - - m_nal_array.push_back(array); -} - - -void Box_vvcC::append_nal_data(const uint8_t* data, size_t size) -{ - std::vector nal; - nal.resize(size); - memcpy(nal.data(), data, size); - - NalArray array; - array.m_array_completeness = 0; - array.m_NAL_unit_type = uint8_t(nal[0] >> 1); - array.m_nal_units.push_back(std::move(nal)); - - m_nal_array.push_back(array); -} - - -Error Box_vvcC::write(StreamWriter& writer) const -{ - size_t box_start = reserve_box_header_space(writer); - - const auto& c = m_configuration; - - writer.write8(c.configurationVersion); - writer.write16(c.avgFrameRate_times_256); - - assert(c.lengthSize == 1 || c.lengthSize == 2 || c.lengthSize == 4); - - uint8_t v = (uint8_t) ((c.constantFrameRate << 6) | - (c.numTemporalLayers << 3) | - ((c.lengthSize - 1) << 1) | - (c.ptl_present_flag ? 1 : 0)); - writer.write8(v); - - if (c.ptl_present_flag) { - assert(false); // TODO - //VvcPTLRecord(numTemporalLayers) track_ptl; - //unsigned int(16) output_layer_set_idx; - } - - v = 0; - if (c.chroma_format_present_flag) { - v |= 0x80 | (c.chroma_format_idc << 5); - } - else { - v |= 0x60; - } - - if (c.bit_depth_present_flag) { - v |= (uint8_t)(0x10 | ((c.bit_depth - 8) << 1)); - } - else { - v |= 0x0e; - } - - v |= 0x01; // reserved - writer.write8(v); - - if (m_nal_array.size() >= 256) { - // TODO: error - } - - if (m_nal_array.size() > 255) { - return {heif_error_Encoding_error, heif_suberror_Unspecified, "Too many VVC NAL arrays."}; - } - - writer.write8((uint8_t)m_nal_array.size()); - for (const NalArray& nal_array : m_nal_array) { - uint8_t v2 = (nal_array.m_array_completeness ? 0x80 : 0); - v2 |= nal_array.m_NAL_unit_type; - writer.write8(v2); - - if (nal_array.m_nal_units.size() > 0xFFFF) { - return {heif_error_Encoding_error, heif_suberror_Unspecified, "Too many VVC NAL units."}; - } - - writer.write16((uint16_t)nal_array.m_nal_units.size()); - for (const auto& nal : nal_array.m_nal_units) { - - if (nal.size() > 0xFFFF) { - return {heif_error_Encoding_error, heif_suberror_Unspecified, "VVC NAL too large."}; - } - - writer.write16((uint16_t)nal.size()); - writer.write(nal); - } - } - - prepend_header(writer, box_start); - - return Error::Ok; -} - - -static const char* vvc_chroma_names[4] = {"mono", "4:2:0", "4:2:2", "4:4:4"}; - -std::string Box_vvcC::dump(Indent& indent) const -{ - std::ostringstream sstr; - sstr << Box::dump(indent); - - const auto& c = m_configuration; // abbreviation - - sstr << indent << "version: " << ((int) c.configurationVersion) << "\n" - << indent << "frame-rate: " << (c.avgFrameRate_times_256 / 256.0f) << "\n" - << indent << "constant frame rate: " << (c.constantFrameRate == 1 ? "constant" : (c.constantFrameRate == 2 ? "multi-layer" : "unknown")) << "\n" - << indent << "num temporal layers: " << ((int) c.numTemporalLayers) << "\n" - << indent << "length size: " << ((int) c.lengthSize) << "\n" - << indent << "chroma-format: "; - if (c.chroma_format_present_flag) { - sstr << vvc_chroma_names[c.chroma_format_idc] << "\n"; - } - else { - sstr << "---\n"; - } - - sstr << indent << "bit-depth: "; - if (c.bit_depth_present_flag) { - sstr << ((int) c.bit_depth) << "\n"; - } - else { - sstr << "---\n"; - } - - sstr << indent << "num of arrays: " << m_nal_array.size() << "\n"; - - sstr << indent << "config NALs:"; - for (size_t i = 0; i < m_nal_array.size(); i++) { - indent++; - sstr << indent << "array completeness: " << ((int)m_nal_array[i].m_array_completeness) << "\n"; - sstr << std::hex << std::setw(2) << std::setfill('0') << m_nal_array[i].m_NAL_unit_type << "\n"; - - for (const auto& nal : m_nal_array[i].m_nal_units) { - std::string ind = indent.get_string(); - sstr << write_raw_data_as_hex(nal.data(), nal.size(), ind, ind); - } - } - sstr << std::dec << std::setw(0) << "\n"; - - return sstr.str(); -} - - -static std::vector remove_start_code_emulation(const uint8_t* sps, size_t size) -{ - std::vector out_data; - - for (size_t i = 0; i < size; i++) { - if (i + 2 < size && - sps[i] == 0 && - sps[i + 1] == 0 && - sps[i + 2] == 3) { - out_data.push_back(0); - out_data.push_back(0); - i += 2; - } - else { - out_data.push_back(sps[i]); - } - } - - return out_data; -} - - - -Error parse_sps_for_vvcC_configuration(const uint8_t* sps, size_t size, - Box_vvcC::configuration* config, - int* width, int* height) -{ - // remove start-code emulation bytes from SPS header stream - - std::vector sps_no_emul = remove_start_code_emulation(sps, size); - - sps = sps_no_emul.data(); - size = sps_no_emul.size(); - - BitReader reader(sps, (int) size); - - // skip NAL header - reader.skip_bits(2 * 8); - - // skip SPS ID - reader.skip_bits(4); - - // skip VPS ID - reader.skip_bits(4); - - config->numTemporalLayers = (uint8_t)(reader.get_bits(3) + 1); - config->chroma_format_idc = (uint8_t)(reader.get_bits(2)); - config->chroma_format_present_flag = true; - reader.skip_bits(2); - - bool sps_ptl_dpb_hrd_params_present_flag = reader.get_bits(1); - if (sps_ptl_dpb_hrd_params_present_flag) { - // profile_tier_level( 1, sps_max_sublayers_minus1 ) - - if (true /*profileTierPresentFlag*/) { - //general_profile_idc - //general_tier_flag - reader.skip_bits(8); - } - reader.skip_bits(8); // general_level_idc - reader.skip_bits(1); //ptl_frame_only_constraint_flag - reader.skip_bits(1); //ptl_multilayer_enabled_flag - if (true /* profileTierPresentFlag*/ ) { - // general_constraints_info() - - bool gci_present_flag = reader.get_bits(1); - if (gci_present_flag) { - assert(false); - } - - reader.skip_to_byte_boundary(); - } - - std::vector ptl_sublayer_level_present_flag(config->numTemporalLayers); - for (int i = config->numTemporalLayers-2; i >= 0; i--) { - ptl_sublayer_level_present_flag[i] = reader.get_bits(1); - } - - reader.skip_to_byte_boundary(); - - for (int i = config->numTemporalLayers-2; i >= 0; i--) { - if (ptl_sublayer_level_present_flag[i]) { - reader.skip_bits(8); // sublayer_level_idc[i] - } - } - - if (true /*profileTierPresentFlag*/) { - int ptl_num_sub_profiles = reader.get_bits(8); - for (int i = 0; i < ptl_num_sub_profiles; i++) { - uint32_t idc = reader.get_bits(32); //general_sub_profile_idc[i] - (void) idc; - } - } - } - - reader.skip_bits(1); // sps_gdr_enabled_flag - bool sps_ref_pic_resampling_enabled_flag = reader.get_bits(1); - if (sps_ref_pic_resampling_enabled_flag) { - reader.skip_bits(1); // sps_res_change_in_clvs_allowed_flag - } - - int sps_pic_width_max_in_luma_samples; - int sps_pic_height_max_in_luma_samples; - - bool success; - success = reader.get_uvlc(&sps_pic_width_max_in_luma_samples); - (void)success; - success = reader.get_uvlc(&sps_pic_height_max_in_luma_samples); - (void)success; - - *width = sps_pic_width_max_in_luma_samples; - *height = sps_pic_height_max_in_luma_samples; - - int sps_conformance_window_flag = reader.get_bits(1); - if (sps_conformance_window_flag) { - assert(false); // TODO - } - - bool sps_subpic_info_present_flag = reader.get_bits(1); - if (sps_subpic_info_present_flag) { - assert(false); // TODO - } - - int bitDepth_minus8; - success = reader.get_uvlc(&bitDepth_minus8); - (void)success; - - if (bitDepth_minus8 > 0xFF - 8) { - return {heif_error_Encoding_error, heif_suberror_Unspecified, "VCC bit depth out of range."}; - } - - config->bit_depth = (uint8_t)(bitDepth_minus8 + 8); - config->bit_depth_present_flag = true; - - return Error::Ok; -} diff --git a/libheif/codecs/vvc_boxes.cc b/libheif/codecs/vvc_boxes.cc new file mode 100644 index 0000000000..10839ab8ad --- /dev/null +++ b/libheif/codecs/vvc_boxes.cc @@ -0,0 +1,553 @@ +/* + * HEIF VVC codec. + * Copyright (c) 2023 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 "vvc_boxes.h" +#include "file.h" +#include +#include +#include +#include +#include +#include + + +Error Box_vvcC::parse(BitstreamRange& range, const heif_security_limits* limits) +{ + parse_full_box_header(range); + + uint8_t byte; + + auto& c = m_configuration; // abbreviation + + byte = range.read8(); + + c.LengthSizeMinusOne = (byte >> 1) & 3; + c.ptl_present_flag = !!(byte & 1); + + if (c.ptl_present_flag) { + uint16_t word = range.read16(); + c.ols_idx = (word >> 7) & 0x1FF; + c.num_sublayers = (word >> 4) & 0x07; + c.constant_frame_rate = (word >> 2) & 0x03; + c.chroma_format_idc = word & 0x03; + + byte = range.read8(); + c.bit_depth_minus8 = (byte >> 5) & 0x07; + + // VvcPTLRecord + + auto& ptl = c.native_ptl; // abbreviation + + byte = range.read8(); + ptl.num_bytes_constraint_info = byte & 0x3f; + + if (ptl.num_bytes_constraint_info == 0) { + return {heif_error_Invalid_input, + heif_suberror_Invalid_parameter_value, + "vvcC with num_bytes_constraint_info==0 is not allowed."}; + } + + byte = range.read8(); + ptl.general_profile_idc = (byte >> 1) & 0x7f; + ptl.general_tier_flag = (byte & 1); + + ptl.general_level_idc = range.read8(); + + for (int i = 0; i < ptl.num_bytes_constraint_info; i++) { + byte = range.read8(); + if (i == 0) { + ptl.ptl_frame_only_constraint_flag = (byte >> 7) & 1; + ptl.ptl_multi_layer_enabled_flag = (byte >> 6) & 1; + byte &= 0x3f; + } + + ptl.general_constraint_info.push_back(byte); + } + + if (c.num_sublayers > 1) { + ptl.ptl_sublayer_level_present_flag.resize(c.num_sublayers - 1); + + byte = range.read8(); + uint8_t mask = 0x80; + + for (int i = c.num_sublayers - 2; i >= 0; i--) { + ptl.ptl_sublayer_level_present_flag[i] = !!(byte & mask); + mask >>= 1; + } + } + + ptl.sublayer_level_idc.resize(c.num_sublayers); + if (c.num_sublayers > 0) { + ptl.sublayer_level_idc[c.num_sublayers - 1] = ptl.general_level_idc; + + for (int i = c.num_sublayers - 2; i >= 0; i--) { + if (ptl.ptl_sublayer_level_present_flag[i]) { + ptl.sublayer_level_idc[i] = range.read8(); + } + else { + ptl.sublayer_level_idc[i] = ptl.sublayer_level_idc[i + 1]; + } + } + } + + uint8_t ptl_num_sub_profiles = range.read8(); + for (int j=0; j < ptl_num_sub_profiles; j++) { + ptl.general_sub_profile_idc.push_back(range.read32()); + } + + + // remaining fields + + c.max_picture_width = range.read16(); + c.max_picture_height = range.read16(); + c.avg_frame_rate = range.read16(); + } + + + // read NAL arrays + + int nArrays = range.read8(); + + for (int i = 0; i < nArrays && !range.error(); i++) { + byte = range.read8(); + + NalArray array; + + array.m_array_completeness = (byte >> 7) & 1; + array.m_NAL_unit_type = (byte & 0x3F); + + int nUnits = range.read16(); + for (int u = 0; u < nUnits && !range.error(); u++) { + + std::vector nal_unit; + int size = range.read16(); + if (!size) { + // Ignore empty NAL units. + continue; + } + + if (range.prepare_read(size)) { + nal_unit.resize(size); + bool success = range.get_istream()->read((char*) nal_unit.data(), size); + if (!success) { + return Error{heif_error_Invalid_input, heif_suberror_End_of_data, "error while reading hvcC box"}; + } + } + + array.m_nal_units.push_back(std::move(nal_unit)); + } + + m_nal_array.push_back(std::move(array)); + } + + return range.get_error(); +} + + +bool Box_vvcC::get_headers(std::vector* dest) const +{ + for (const auto& nal_array : m_nal_array) { + for (const auto& nal : nal_array.m_nal_units) { + assert(nal.size() <= 0xFFFF); + auto size = static_cast(nal.size()); + + dest->push_back(0); + dest->push_back(0); + dest->push_back(static_cast(size >> 8)); + dest->push_back(static_cast(size & 0xFF)); + + dest->insert(dest->end(), nal.begin(), nal.end()); + } + } + + return true; +} + + +void Box_vvcC::append_nal_data(const std::vector& nal) +{ + assert(nal.size()>=2); + uint8_t nal_type = (nal[1] >> 3) & 0x1F; + + // insert into existing array if it exists + + for (auto& nalarray : m_nal_array) { + if (nalarray.m_NAL_unit_type == nal_type) { + nalarray.m_nal_units.push_back(nal); + return; + } + } + + // generate new NAL array + + NalArray array; + array.m_array_completeness = true; + array.m_NAL_unit_type = uint8_t((nal[1] >> 3) & 0x1F); + array.m_nal_units.push_back(nal); + + m_nal_array.push_back(array); +} + + +void Box_vvcC::append_nal_data(const uint8_t* data, size_t size) +{ + std::vector nal; + nal.resize(size); + memcpy(nal.data(), data, size); + + append_nal_data(nal); +} + + +Error Box_vvcC::write(StreamWriter& writer) const +{ + size_t box_start = reserve_box_header_space(writer); + + const auto& c = m_configuration; + + uint8_t byte; + + byte = uint8_t(0xF8 | (c.LengthSizeMinusOne<<1) | (c.ptl_present_flag ? 1 : 0)); + writer.write8(byte); + + if (c.ptl_present_flag) { + assert(c.ols_idx <= 0x1FF); + assert(c.num_sublayers <= 7); + assert(c.constant_frame_rate <= 3); + assert(c.chroma_format_idc <= 3); + assert(c.bit_depth_minus8 <= 7); + + auto word = uint16_t((c.ols_idx << 7) | (c.num_sublayers << 4) | (c.constant_frame_rate << 2) | (c.chroma_format_idc)); + writer.write16(word); + + writer.write8(uint8_t((c.bit_depth_minus8<<5) | 0x1F)); + + const auto& ptl = c.native_ptl; + + assert(ptl.general_profile_idc <= 0x7F); + + writer.write8(ptl.num_bytes_constraint_info & 0x3f); + writer.write8(static_cast((ptl.general_profile_idc<<1) | ptl.general_tier_flag)); + writer.write8(ptl.general_level_idc); + + for (int i=0;i((ptl.ptl_frame_only_constraint_flag << 7) | (ptl.ptl_multi_layer_enabled_flag << 6) | ptl.general_constraint_info[0]); + } + else { + byte = ptl.general_constraint_info[i]; + } + + writer.write8(byte); + } + + byte = 0; + if (c.num_sublayers > 1) { + uint8_t mask=0x80; + + for (int i = c.num_sublayers - 2; i >= 0; i--) { + if (ptl.ptl_sublayer_level_present_flag[i]) { + byte |= mask; + } + mask >>= 1; + } + } + writer.write8(byte); + + for (int i=c.num_sublayers-2; i >= 0; i--) { + if (ptl.ptl_sublayer_level_present_flag[i]) { + writer.write8(ptl.sublayer_level_idc[i]); + } + } + + assert(ptl.general_sub_profile_idc.size() <= 0xFF); + byte = static_cast(ptl.general_sub_profile_idc.size()); + writer.write8(byte); + + for (int j=0; j < byte; j++) { + writer.write32(ptl.general_sub_profile_idc[j]); + } + + writer.write16(c.max_picture_width); + writer.write16(c.max_picture_height); + writer.write16(c.avg_frame_rate); + } + + // --- write configuration NALs + + if (m_nal_array.size() > 255) { + return {heif_error_Encoding_error, heif_suberror_Unspecified, "Too many VVC NAL arrays."}; + } + + writer.write8((uint8_t)m_nal_array.size()); + for (const NalArray& nal_array : m_nal_array) { + uint8_t v2 = (nal_array.m_array_completeness ? 0x80 : 0); + v2 |= nal_array.m_NAL_unit_type; + writer.write8(v2); + + if (nal_array.m_nal_units.size() > 0xFFFF) { + return {heif_error_Encoding_error, heif_suberror_Unspecified, "Too many VVC NAL units."}; + } + + writer.write16((uint16_t)nal_array.m_nal_units.size()); + for (const auto& nal : nal_array.m_nal_units) { + + if (nal.size() > 0xFFFF) { + return {heif_error_Encoding_error, heif_suberror_Unspecified, "VVC NAL too large."}; + } + + writer.write16((uint16_t)nal.size()); + writer.write(nal); + } + } + + prepend_header(writer, box_start); + + return Error::Ok; +} + + +static const char* vvc_chroma_names[4] = {"mono", "4:2:0", "4:2:2", "4:4:4"}; + +const char* NAL_name(uint8_t nal_type) +{ + switch (nal_type) { + case 12: return "OPI"; + case 13: return "DCI"; + case 14: return "VPS"; + case 15: return "SPS"; + case 16: return "PPS"; + case 17: return "PREFIX_APS"; + case 18: return "SUFFIX_APS"; + case 19: return "PH"; + default: return "?"; + } +} + + +std::string Box_vvcC::dump(Indent& indent) const +{ + std::ostringstream sstr; + sstr << FullBox::dump(indent); + + const auto& c = m_configuration; // abbreviation + + sstr << indent << "NAL length size: " << ((int) c.LengthSizeMinusOne + 1) << "\n"; + if (c.ptl_present_flag) { + const auto& ptl = c.native_ptl; + sstr << indent << "ols-index: " << c.ols_idx << "\n" + << indent << "num sublayers: " << ((int) c.num_sublayers) << "\n" + << indent << "constant frame rate: " << (c.constant_frame_rate == 1 ? "constant" : (c.constant_frame_rate == 2 ? "multi-layer" : "unknown")) << "\n" + << indent << "chroma-format: " << vvc_chroma_names[c.chroma_format_idc] << "\n" + << indent << "bit-depth: " << ((int) c.bit_depth_minus8 + 8) << "\n" + << indent << "max picture width: " << c.max_picture_width << "\n" + << indent << "max picture height: " << c.max_picture_height << "\n"; + + sstr << indent << "general profile: " << ((int)ptl.general_profile_idc) << "\n" + << indent << "tier flag: " << ((int)ptl.general_tier_flag) << "\n" + << indent << "general level:" << ((int)ptl.general_level_idc) << "\n" + << indent << "ptl frame only constraint flag: " << ((int)ptl.ptl_frame_only_constraint_flag) << "\n" + << indent << "ptl multi layer enabled flag: " << ((int)ptl.ptl_multi_layer_enabled_flag) << "\n"; + } + + + sstr << indent << "num of arrays: " << m_nal_array.size() << "\n"; + + sstr << indent << "config NALs:\n"; + for (const auto& nal_array : m_nal_array) { + indent++; + sstr << indent << "NAL type: " << ((int)nal_array.m_NAL_unit_type) << " (" << NAL_name(nal_array.m_NAL_unit_type) << ")\n"; + sstr << indent << "array completeness: " << ((int)nal_array.m_array_completeness) << "\n"; + + for (const auto& nal : nal_array.m_nal_units) { + indent++; + std::string ind = indent.get_string(); + sstr << write_raw_data_as_hex(nal.data(), nal.size(), ind, ind); + indent--; + } + indent--; + } + + return sstr.str(); +} + +static std::vector remove_start_code_emulation(const uint8_t* sps, size_t size) +{ + std::vector out_data; + + for (size_t i = 0; i < size; i++) { + if (i + 2 < size && + sps[i] == 0 && + sps[i + 1] == 0 && + sps[i + 2] == 3) { + out_data.push_back(0); + out_data.push_back(0); + i += 2; + } + else { + out_data.push_back(sps[i]); + } + } + + return out_data; +} + + + +Error parse_sps_for_vvcC_configuration(const uint8_t* sps, size_t size, + Box_vvcC::configuration* config, + int* width, int* height) +{ + // remove start-code emulation bytes from SPS header stream + + std::vector sps_no_emul = remove_start_code_emulation(sps, size); + + sps = sps_no_emul.data(); + size = sps_no_emul.size(); + + BitReader reader(sps, (int) size); + + // skip NAL header + reader.skip_bits(2 * 8); + + // skip SPS ID + reader.skip_bits(4); + + // skip VPS ID + reader.skip_bits(4); + + config->ols_idx = 0; + config->num_sublayers = reader.get_bits8(3) + 1; + config->chroma_format_idc = reader.get_bits8(2); + reader.skip_bits(2); + + bool sps_ptl_dpb_hrd_params_present_flag = reader.get_bits(1); + if (sps_ptl_dpb_hrd_params_present_flag) { + // profile_tier_level( 1, sps_max_sublayers_minus1 ) + + auto& ptl = config->native_ptl; + + if (true /*profileTierPresentFlag*/) { + ptl.general_profile_idc = reader.get_bits8(7); + ptl.general_tier_flag = reader.get_bits8(1); + } + ptl.general_level_idc = reader.get_bits8(8); + ptl.ptl_frame_only_constraint_flag = reader.get_bits8(1); + ptl.ptl_multi_layer_enabled_flag = reader.get_bits8(1); + + if (true /* profileTierPresentFlag*/ ) { + // general_constraints_info() + + bool gci_present_flag = reader.get_bits(1); + if (gci_present_flag) { + assert(false); + } + else { + ptl.num_bytes_constraint_info = 1; + ptl.general_constraint_info.push_back(0); + } + + reader.skip_to_byte_boundary(); + } + + ptl.ptl_sublayer_level_present_flag.resize(config->num_sublayers); + for (int i = config->num_sublayers-2; i >= 0; i--) { + ptl.ptl_sublayer_level_present_flag[i] = reader.get_bits(1); + } + + reader.skip_to_byte_boundary(); + + ptl.sublayer_level_idc.resize(config->num_sublayers); + for (int i = config->num_sublayers-2; i >= 0; i--) { + if (ptl.ptl_sublayer_level_present_flag[i]) { + ptl.sublayer_level_idc[i] = reader.get_bits8(8); + } + } + + if (true /*profileTierPresentFlag*/) { + int ptl_num_sub_profiles = reader.get_bits(8); + ptl.general_sub_profile_idc.resize(ptl_num_sub_profiles); + + for (int i = 0; i < ptl_num_sub_profiles; i++) { + ptl.general_sub_profile_idc[i] = reader.get_bits(32); + } + } + } + + reader.skip_bits(1); // sps_gdr_enabled_flag + bool sps_ref_pic_resampling_enabled_flag = reader.get_bits(1); + if (sps_ref_pic_resampling_enabled_flag) { + reader.skip_bits(1); // sps_res_change_in_clvs_allowed_flag + } + + int sps_pic_width_max_in_luma_samples; + int sps_pic_height_max_in_luma_samples; + + bool success; + success = reader.get_uvlc(&sps_pic_width_max_in_luma_samples); + (void)success; + success = reader.get_uvlc(&sps_pic_height_max_in_luma_samples); + (void)success; + + *width = sps_pic_width_max_in_luma_samples; + *height = sps_pic_height_max_in_luma_samples; + + if (sps_pic_width_max_in_luma_samples > 0xFFFF || + sps_pic_height_max_in_luma_samples > 0xFFFF) { + return {heif_error_Encoding_error, + heif_suberror_Invalid_parameter_value, + "SPS max picture width or height exceeds maximum (65535)"}; + } + + config->max_picture_width = static_cast(sps_pic_width_max_in_luma_samples); + config->max_picture_height = static_cast(sps_pic_height_max_in_luma_samples); + + int sps_conformance_window_flag = reader.get_bits(1); + if (sps_conformance_window_flag) { + int left,right,top,bottom; + reader.get_uvlc(&left); + reader.get_uvlc(&right); + reader.get_uvlc(&top); + reader.get_uvlc(&bottom); + } + + bool sps_subpic_info_present_flag = reader.get_bits(1); + if (sps_subpic_info_present_flag) { + assert(false); // TODO + } + + int bitDepth_minus8; + success = reader.get_uvlc(&bitDepth_minus8); + (void)success; + + if (bitDepth_minus8 > 0xFF - 8) { + return {heif_error_Encoding_error, heif_suberror_Unspecified, "VCC bit depth out of range."}; + } + + config->bit_depth_minus8 = static_cast(bitDepth_minus8); + + config->constant_frame_rate = 1; // is constant (TODO: where do we get this from) + + return Error::Ok; +} + diff --git a/libheif/codecs/vvc.h b/libheif/codecs/vvc_boxes.h similarity index 53% rename from libheif/codecs/vvc.h rename to libheif/codecs/vvc_boxes.h index 443009c5c5..bf6322306d 100644 --- a/libheif/codecs/vvc.h +++ b/libheif/codecs/vvc_boxes.h @@ -18,15 +18,17 @@ * along with libheif. If not, see . */ -#ifndef LIBHEIF_VVC_H -#define LIBHEIF_VVC_H +#ifndef LIBHEIF_VVC_BOXES_H +#define LIBHEIF_VVC_BOXES_H #include "box.h" #include #include +#include "image-items/image_item.h" +#include -class Box_vvcC : public Box +class Box_vvcC : public FullBox { public: Box_vvcC() @@ -34,37 +36,44 @@ class Box_vvcC : public Box set_short_type(fourcc("vvcC")); } + bool is_essential() const override { return true; } + + struct VvcPTLRecord { + uint8_t num_bytes_constraint_info; // 6 bits + uint8_t general_profile_idc; // 7 bits + uint8_t general_tier_flag; // 1 bit + uint8_t general_level_idc; // 8 bits + uint8_t ptl_frame_only_constraint_flag; // 1 bit + uint8_t ptl_multi_layer_enabled_flag; // 1 bit + std::vector general_constraint_info; + + std::vector ptl_sublayer_level_present_flag; // TODO: should we save this here or can we simply derive it on the fly? + + std::vector sublayer_level_idc; + std::vector general_sub_profile_idc; + }; + struct configuration { - uint8_t configurationVersion = 1; - uint16_t avgFrameRate_times_256 = 0; - uint8_t constantFrameRate = 1; // 2 bits - uint8_t numTemporalLayers = 1; // 3 bits - uint8_t lengthSize = 1; // 2 bits - bool ptl_present_flag = false; - //if (ptl_present_flag) { - // VvcPTLRecord(numTemporalLayers) track_ptl; - // uint16_t output_layer_set_idx; - //} - bool chroma_format_present_flag = false; - uint8_t chroma_format_idc; - - bool bit_depth_present_flag = false; - uint8_t bit_depth; + uint8_t LengthSizeMinusOne = 3; // 0,1,3 default: 4 bytes for NAL unit lengths + bool ptl_present_flag = true; + + // only of PTL present + uint16_t ols_idx; // 9 bits + uint8_t num_sublayers; // 3 bits + uint8_t constant_frame_rate; // 2 bits + uint8_t chroma_format_idc; // 2 bits + uint8_t bit_depth_minus8; // 3 bits + struct VvcPTLRecord native_ptl; + uint16_t max_picture_width; + uint16_t max_picture_height; + uint16_t avg_frame_rate; }; std::string dump(Indent&) const override; - bool get_headers(std::vector* dest) const - { - // TODO - -#if 0 - *dest = m_config_NALs; -#endif - return true; - } + bool get_headers(std::vector* dest) const; void set_configuration(const configuration& config) { m_configuration = config; } @@ -76,7 +85,7 @@ class Box_vvcC : public Box Error write(StreamWriter& writer) const override; protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits* limits) override; private: struct NalArray @@ -84,14 +93,11 @@ class Box_vvcC : public Box bool m_array_completeness; uint8_t m_NAL_unit_type; - std::vector > m_nal_units; + std::vector > m_nal_units; // only one NAL item for DCI and OPI }; configuration m_configuration; - //uint8_t m_length_size = 4; // default: 4 bytes for NAL unit lengths - std::vector m_nal_array; - //std::vector m_config_NALs; }; @@ -99,5 +105,4 @@ Error parse_sps_for_vvcC_configuration(const uint8_t* sps, size_t size, Box_vvcC::configuration* inout_config, int* width, int* height); - -#endif // LIBHEIF_VVC_H +#endif // LIBHEIF_VVC_BOXES_H diff --git a/libheif/codecs/vvc_dec.cc b/libheif/codecs/vvc_dec.cc new file mode 100644 index 0000000000..3c929713a9 --- /dev/null +++ b/libheif/codecs/vvc_dec.cc @@ -0,0 +1,71 @@ +/* + * HEIF codec. + * Copyright (c) 2024 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 "vvc_dec.h" +#include "vvc_boxes.h" +#include "error.h" +#include "context.h" + +#include + + +Result> Decoder_VVC::read_bitstream_configuration_data() const +{ + std::vector data; + if (!m_vvcC->get_headers(&data)) { + return Error{heif_error_Invalid_input, + heif_suberror_No_item_data}; + } + + return data; +} + + +int Decoder_VVC::get_luma_bits_per_pixel() const +{ + const Box_vvcC::configuration& config = m_vvcC->get_configuration(); + if (config.ptl_present_flag) { + return config.bit_depth_minus8 + 8; + } + else { + return 8; // TODO: what shall we do if the bit-depth is unknown? Use PIXI? + } +} + + +int Decoder_VVC::get_chroma_bits_per_pixel() const +{ + return get_luma_bits_per_pixel(); +} + + +Error Decoder_VVC::get_coded_image_colorspace(heif_colorspace* out_colorspace, heif_chroma* out_chroma) const +{ + *out_chroma = (heif_chroma) (m_vvcC->get_configuration().chroma_format_idc); + + if (*out_chroma == heif_chroma_monochrome) { + *out_colorspace = heif_colorspace_monochrome; + } + else { + *out_colorspace = heif_colorspace_YCbCr; + } + + return Error::Ok; +} diff --git a/libheif/codecs/vvc_dec.h b/libheif/codecs/vvc_dec.h new file mode 100644 index 0000000000..0d55b1c39a --- /dev/null +++ b/libheif/codecs/vvc_dec.h @@ -0,0 +1,54 @@ +/* + * HEIF codec. + * Copyright (c) 2024 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 HEIF_VVC_DEC_H +#define HEIF_VVC_DEC_H + +#include "libheif/heif.h" +#include "box.h" +#include "error.h" + +#include +#include +#include + +class Box_vvcC; + + +class Decoder_VVC : public Decoder +{ +public: + explicit Decoder_VVC(const std::shared_ptr& vvcC) : m_vvcC(vvcC) {} + + heif_compression_format get_compression_format() const override { return heif_compression_VVC; } + + int get_luma_bits_per_pixel() const override; + + int get_chroma_bits_per_pixel() const override; + + Error get_coded_image_colorspace(heif_colorspace*, heif_chroma*) const override; + + Result> read_bitstream_configuration_data() const override; + +private: + const std::shared_ptr m_vvcC; +}; + +#endif diff --git a/libheif/color-conversion/alpha.cc b/libheif/color-conversion/alpha.cc index 73a9f51c14..964f9dbdd6 100644 --- a/libheif/color-conversion/alpha.cc +++ b/libheif/color-conversion/alpha.cc @@ -58,8 +58,8 @@ Op_drop_alpha_plane::convert_colorspace(const std::shared_ptrget_width(); - int height = input->get_height(); + uint32_t width = input->get_width(); + uint32_t height = input->get_height(); auto outimg = std::make_shared(); diff --git a/libheif/color-conversion/chroma_sampling.cc b/libheif/color-conversion/chroma_sampling.cc index 7619c26001..015d1cdce2 100644 --- a/libheif/color-conversion/chroma_sampling.cc +++ b/libheif/color-conversion/chroma_sampling.cc @@ -44,7 +44,7 @@ Op_YCbCr444_to_YCbCr420_average::state_after_conversion(const ColorState& bool hdr = !std::is_same::value; - if ((input_state.bits_per_pixel != 8) != hdr) { + if ((input_state.bits_per_pixel > 8) != hdr) { return {}; } @@ -95,16 +95,16 @@ Op_YCbCr444_to_YCbCr420_average::convert_colorspace(const std::shared_ptr } if (!hdr) { - if (bpp_y != 8 || - bpp_cb != 8 || - bpp_cr != 8) { + if (bpp_y > 8 || + bpp_cb > 8 || + bpp_cr > 8) { return nullptr; } } else { - if (bpp_y == 8 || - bpp_cb == 8 || - bpp_cr == 8) { + if (bpp_y <= 8 || + bpp_cb <= 8 || + bpp_cr <= 8) { return nullptr; } } @@ -119,15 +119,15 @@ Op_YCbCr444_to_YCbCr420_average::convert_colorspace(const std::shared_ptr auto colorProfile = input->get_color_profile_nclx(); - int width = input->get_width(); - int height = input->get_height(); + uint32_t width = input->get_width(); + uint32_t height = input->get_height(); auto outimg = std::make_shared(); outimg->create(width, height, heif_colorspace_YCbCr, heif_chroma_420); - int cwidth = (width + 1) / 2; - int cheight = (height + 1) / 2; + uint32_t cwidth = (width + 1) / 2; + uint32_t cheight = (height + 1) / 2; if (!outimg->add_plane(heif_channel_Y, width, height, bpp_y) || !outimg->add_plane(heif_channel_Cb, cwidth, cheight, bpp_cb) || @@ -142,10 +142,10 @@ Op_YCbCr444_to_YCbCr420_average::convert_colorspace(const std::shared_ptr } const Pixel* in_y, * in_cb, * in_cr, * in_a; - int in_y_stride = 0, in_cb_stride = 0, in_cr_stride = 0, in_a_stride = 0; + uint32_t in_y_stride = 0, in_cb_stride = 0, in_cr_stride = 0, in_a_stride = 0; Pixel* out_y, * out_cb, * out_cr, * out_a; - int out_y_stride = 0, out_cb_stride = 0, out_cr_stride = 0, out_a_stride = 0; + uint32_t out_y_stride = 0, out_cb_stride = 0, out_cr_stride = 0, out_a_stride = 0; in_y = (const Pixel*) input->get_plane(heif_channel_Y, &in_y_stride); in_cb = (const Pixel*) input->get_plane(heif_channel_Cb, &in_cb_stride); @@ -178,7 +178,7 @@ Op_YCbCr444_to_YCbCr420_average::convert_colorspace(const std::shared_ptr // --- fill right and bottom borders if the image size is odd if (height & 1) { - for (int x = 0; x < width - 1; x += 2) { + for (uint32_t x = 0; x < width - 1; x += 2) { out_cb[(cheight - 1) * out_cb_stride + x / 2] = (Pixel) ((in_cb[(height - 1) * in_cb_stride + x] + in_cb[(height - 1) * in_cb_stride + x + 1] + 1) / 2); out_cr[(cheight - 1) * out_cr_stride + x / 2] = (Pixel) ((in_cr[(height - 1) * in_cr_stride + x] + @@ -187,7 +187,7 @@ Op_YCbCr444_to_YCbCr420_average::convert_colorspace(const std::shared_ptr } if (width & 1) { - for (int y = 0; y < height - 1; y += 2) { + for (uint32_t y = 0; y < height - 1; y += 2) { out_cb[(y / 2) * out_cb_stride + cwidth - 1] = (Pixel) ((in_cb[(y + 0) * in_cb_stride + width - 1] + in_cb[(y + 1) * in_cb_stride + width - 1] + 1) / 2); out_cr[(y / 2) * out_cr_stride + cwidth - 1] = (Pixel) ((in_cr[(y + 0) * in_cr_stride + width - 1] + @@ -203,7 +203,7 @@ Op_YCbCr444_to_YCbCr420_average::convert_colorspace(const std::shared_ptr // --- averaging filter - int x, y; + uint32_t x, y; for (y = 0; y < height - 1; y += 2) { for (x = 0; x < width - 1; x += 2) { Pixel cb00 = in_cb[y * in_cb_stride + x]; @@ -223,7 +223,7 @@ Op_YCbCr444_to_YCbCr420_average::convert_colorspace(const std::shared_ptr // TODO: check whether we can use HeifPixelImage::transfer_plane_from_image_as() instead of copying Y and Alpha for (y = 0; y < height; y++) { - int copyWidth = (hdr ? width * 2 : width); + uint32_t copyWidth = (hdr ? width * 2 : width); memcpy(&out_y[y * out_y_stride], &in_y[y * in_y_stride], copyWidth); @@ -262,7 +262,7 @@ Op_YCbCr444_to_YCbCr422_average::state_after_conversion(const ColorState& bool hdr = !std::is_same::value; - if ((input_state.bits_per_pixel != 8) != hdr) { + if ((input_state.bits_per_pixel > 8) != hdr) { return {}; } @@ -313,16 +313,16 @@ Op_YCbCr444_to_YCbCr422_average::convert_colorspace(const std::shared_ptr } if (!hdr) { - if (bpp_y != 8 || - bpp_cb != 8 || - bpp_cr != 8) { + if (bpp_y > 8 || + bpp_cb > 8 || + bpp_cr > 8) { return nullptr; } } else { - if (bpp_y == 8 || - bpp_cb == 8 || - bpp_cr == 8) { + if (bpp_y <= 8 || + bpp_cb <= 8 || + bpp_cr <= 8) { return nullptr; } } @@ -337,15 +337,15 @@ Op_YCbCr444_to_YCbCr422_average::convert_colorspace(const std::shared_ptr auto colorProfile = input->get_color_profile_nclx(); - int width = input->get_width(); - int height = input->get_height(); + uint32_t width = input->get_width(); + uint32_t height = input->get_height(); auto outimg = std::make_shared(); outimg->create(width, height, heif_colorspace_YCbCr, heif_chroma_422); - int cwidth = (width + 1) / 2; - int cheight = height; + uint32_t cwidth = (width + 1) / 2; + uint32_t cheight = height; if (!outimg->add_plane(heif_channel_Y, width, height, bpp_y) || !outimg->add_plane(heif_channel_Cb, cwidth, cheight, bpp_cb) || @@ -360,10 +360,10 @@ Op_YCbCr444_to_YCbCr422_average::convert_colorspace(const std::shared_ptr } const Pixel* in_y, * in_cb, * in_cr, * in_a; - int in_y_stride = 0, in_cb_stride = 0, in_cr_stride = 0, in_a_stride = 0; + uint32_t in_y_stride = 0, in_cb_stride = 0, in_cr_stride = 0, in_a_stride = 0; Pixel* out_y, * out_cb, * out_cr, * out_a; - int out_y_stride = 0, out_cb_stride = 0, out_cr_stride = 0, out_a_stride = 0; + uint32_t out_y_stride = 0, out_cb_stride = 0, out_cr_stride = 0, out_a_stride = 0; in_y = (const Pixel*) input->get_plane(heif_channel_Y, &in_y_stride); in_cb = (const Pixel*) input->get_plane(heif_channel_Cb, &in_cb_stride); @@ -396,7 +396,7 @@ Op_YCbCr444_to_YCbCr422_average::convert_colorspace(const std::shared_ptr // --- fill right border if the image size is odd if (width & 1) { - for (int y = 0; y < height - 1; y++) { + for (uint32_t y = 0; y < height - 1; y++) { out_cb[y * out_cb_stride + cwidth - 1] = (Pixel) in_cb[y * in_cb_stride + width - 1]; out_cr[y * out_cr_stride + cwidth - 1] = (Pixel) in_cr[y * in_cr_stride + width - 1]; } @@ -405,7 +405,7 @@ Op_YCbCr444_to_YCbCr422_average::convert_colorspace(const std::shared_ptr // --- averaging filter - int x, y; + uint32_t x, y; for (y = 0; y < height; y++) { for (x = 0; x < width - 1; x += 2) { Pixel cb00 = in_cb[y * in_cb_stride + x]; @@ -421,7 +421,7 @@ Op_YCbCr444_to_YCbCr422_average::convert_colorspace(const std::shared_ptr // TODO: check whether we can use HeifPixelImage::transfer_plane_from_image_as() instead of copying Y and Alpha for (y = 0; y < height; y++) { - int copyWidth = (hdr ? width * 2 : width); + uint32_t copyWidth = (hdr ? width * 2 : width); memcpy(&out_y[y * out_y_stride], &in_y[y * in_y_stride], copyWidth); @@ -460,7 +460,7 @@ Op_YCbCr420_bilinear_to_YCbCr444::state_after_conversion(const ColorState bool hdr = !std::is_same::value; - if ((input_state.bits_per_pixel != 8) != hdr) { + if ((input_state.bits_per_pixel > 8) != hdr) { return {}; } @@ -507,16 +507,16 @@ Op_YCbCr420_bilinear_to_YCbCr444::convert_colorspace(const std::shared_pt } if (!hdr) { - if (bpp_y != 8 || - bpp_cb != 8 || - bpp_cr != 8) { + if (bpp_y > 8 || + bpp_cb > 8 || + bpp_cr > 8) { return nullptr; } } else { - if (bpp_y == 8 || - bpp_cb == 8 || - bpp_cr == 8) { + if (bpp_y <= 8 || + bpp_cb <= 8 || + bpp_cr <= 8) { return nullptr; } } @@ -531,8 +531,8 @@ Op_YCbCr420_bilinear_to_YCbCr444::convert_colorspace(const std::shared_pt auto colorProfile = input->get_color_profile_nclx(); - int width = input->get_width(); - int height = input->get_height(); + uint32_t width = input->get_width(); + uint32_t height = input->get_height(); auto outimg = std::make_shared(); @@ -551,10 +551,10 @@ Op_YCbCr420_bilinear_to_YCbCr444::convert_colorspace(const std::shared_pt } const Pixel* in_y, * in_cb, * in_cr, * in_a; - int in_y_stride = 0, in_cb_stride = 0, in_cr_stride = 0, in_a_stride = 0; + uint32_t in_y_stride = 0, in_cb_stride = 0, in_cr_stride = 0, in_a_stride = 0; Pixel* out_y, * out_cb, * out_cr, * out_a; - int out_y_stride = 0, out_cb_stride = 0, out_cr_stride = 0, out_a_stride = 0; + uint32_t out_y_stride = 0, out_cb_stride = 0, out_cr_stride = 0, out_a_stride = 0; in_y = (const Pixel*) input->get_plane(heif_channel_Y, &in_y_stride); in_cb = (const Pixel*) input->get_plane(heif_channel_Cb, &in_cb_stride); @@ -611,7 +611,7 @@ Op_YCbCr420_bilinear_to_YCbCr444::convert_colorspace(const std::shared_pt out_cr[0] = in_cr[0]; // top border - for (int cx = 0; cx < (width - 1) / 2; cx++) { + for (uint32_t cx = 0; cx < (width - 1) / 2; cx++) { out_cb[0 * out_cb_stride + 2 * cx + 1] = (Pixel) ((3 * in_cb[cx / 2] + 1 * in_cb[cx / 2 + 1] + 2) / 4); out_cb[0 * out_cb_stride + 2 * cx + 2] = (Pixel) ((1 * in_cb[cx / 2] + 3 * in_cb[cx / 2 + 1] + 2) / 4); out_cr[0 * out_cr_stride + 2 * cx + 1] = (Pixel) ((3 * in_cr[cx / 2] + 1 * in_cr[cx / 2 + 1] + 2) / 4); @@ -625,7 +625,7 @@ Op_YCbCr420_bilinear_to_YCbCr444::convert_colorspace(const std::shared_pt } // left border - for (int cy = 0; cy < (height - 1) / 2; cy++) { + for (uint32_t cy = 0; cy < (height - 1) / 2; cy++) { out_cb[(2 * cy + 1) * out_cb_stride + 0] = (Pixel) ((3 * in_cb[cy / 2 * in_cb_stride] + 1 * in_cb[(cy / 2 + 1) * in_cb_stride] + 2) / 4); out_cb[(2 * cy + 2) * out_cb_stride + 0] = (Pixel) ((1 * in_cb[cy / 2 * in_cb_stride] + 3 * in_cb[(cy / 2 + 1) * in_cb_stride] + 2) / 4); out_cr[(2 * cy + 1) * out_cr_stride + 0] = (Pixel) ((3 * in_cr[cy / 2 * in_cr_stride] + 1 * in_cr[(cy / 2 + 1) * in_cr_stride] + 2) / 4); @@ -640,7 +640,7 @@ Op_YCbCr420_bilinear_to_YCbCr444::convert_colorspace(const std::shared_pt // right border if (width % 2 == 0) { - for (int cy = 0; cy < (height - 1) / 2; cy++) { + for (uint32_t cy = 0; cy < (height - 1) / 2; cy++) { out_cb[(2 * cy + 1) * out_cb_stride + width - 1] = (Pixel) ((3 * in_cb[cy / 2 * in_cb_stride + width / 2 - 1] + 1 * in_cb[(cy / 2 + 1) * in_cb_stride + width / 2 - 1] + 2) / 4); out_cb[(2 * cy + 2) * out_cb_stride + width - 1] = (Pixel) ((1 * in_cb[cy / 2 * in_cb_stride + width / 2 - 1] + 3 * in_cb[(cy / 2 + 1) * in_cb_stride + width / 2 - 1] + 2) / 4); out_cr[(2 * cy + 1) * out_cr_stride + width - 1] = (Pixel) ((3 * in_cr[cy / 2 * in_cr_stride + width / 2 - 1] + 1 * in_cr[(cy / 2 + 1) * in_cr_stride + width / 2 - 1] + 2) / 4); @@ -650,7 +650,7 @@ Op_YCbCr420_bilinear_to_YCbCr444::convert_colorspace(const std::shared_pt // bottom border if (height % 2 == 0) { - for (int cx = 0; cx < (width - 1) / 2; cx++) { + for (uint32_t cx = 0; cx < (width - 1) / 2; cx++) { out_cb[(height - 1) * out_cb_stride + 2 * cx + 1] = (Pixel) ((3 * in_cb[(height / 2 - 1) * in_cb_stride + cx / 2] + 1 * in_cb[(height / 2 - 1) * in_cb_stride + cx / 2 + 1] + 2) / 4); out_cb[(height - 1) * out_cb_stride + 2 * cx + 2] = (Pixel) ((1 * in_cb[(height / 2 - 1) * in_cb_stride + cx / 2] + 3 * in_cb[(height / 2 - 1) * in_cb_stride + cx / 2 + 1] + 2) / 4); out_cr[(height - 1) * out_cr_stride + 2 * cx + 1] = (Pixel) ((3 * in_cr[(height / 2 - 1) * in_cr_stride + cx / 2] + 1 * in_cr[(height / 2 - 1) * in_cr_stride + cx / 2 + 1] + 2) / 4); @@ -667,11 +667,11 @@ Op_YCbCr420_bilinear_to_YCbCr444::convert_colorspace(const std::shared_pt // --- bilinear filtering of inner part - int x, y; + uint32_t x, y; for (y = 1; y < height - 1; y += 2) { for (x = 1; x < width - 1; x += 2) { - int cx = x / 2; - int cy = y / 2; + uint32_t cx = x / 2; + uint32_t cy = y / 2; Pixel cb00 = in_cb[cy * in_cb_stride + cx]; Pixel cr00 = in_cr[cy * in_cr_stride + cx]; @@ -697,7 +697,7 @@ Op_YCbCr420_bilinear_to_YCbCr444::convert_colorspace(const std::shared_pt // TODO: check whether we can use HeifPixelImage::transfer_plane_from_image_as() instead of copying Y and Alpha for (y = 0; y < height; y++) { - int copyWidth = (hdr ? width * 2 : width); + uint32_t copyWidth = (hdr ? width * 2 : width); memcpy(&out_y[y * out_y_stride], &in_y[y * in_y_stride], copyWidth); @@ -737,7 +737,7 @@ Op_YCbCr422_bilinear_to_YCbCr444::state_after_conversion(const ColorState bool hdr = !std::is_same::value; - if ((input_state.bits_per_pixel != 8) != hdr) { + if ((input_state.bits_per_pixel > 8) != hdr) { return {}; } @@ -784,16 +784,16 @@ Op_YCbCr422_bilinear_to_YCbCr444::convert_colorspace(const std::shared_pt } if (!hdr) { - if (bpp_y != 8 || - bpp_cb != 8 || - bpp_cr != 8) { + if (bpp_y > 8 || + bpp_cb > 8 || + bpp_cr > 8) { return nullptr; } } else { - if (bpp_y == 8 || - bpp_cb == 8 || - bpp_cr == 8) { + if (bpp_y <= 8 || + bpp_cb <= 8 || + bpp_cr <= 8) { return nullptr; } } @@ -808,8 +808,8 @@ Op_YCbCr422_bilinear_to_YCbCr444::convert_colorspace(const std::shared_pt auto colorProfile = input->get_color_profile_nclx(); - int width = input->get_width(); - int height = input->get_height(); + uint32_t width = input->get_width(); + uint32_t height = input->get_height(); auto outimg = std::make_shared(); @@ -828,10 +828,10 @@ Op_YCbCr422_bilinear_to_YCbCr444::convert_colorspace(const std::shared_pt } const Pixel* in_y, * in_cb, * in_cr, * in_a; - int in_y_stride = 0, in_cb_stride = 0, in_cr_stride = 0, in_a_stride = 0; + uint32_t in_y_stride = 0, in_cb_stride = 0, in_cr_stride = 0, in_a_stride = 0; Pixel* out_y, * out_cb, * out_cr, * out_a; - int out_y_stride = 0, out_cb_stride = 0, out_cr_stride = 0, out_a_stride = 0; + uint32_t out_y_stride = 0, out_cb_stride = 0, out_cr_stride = 0, out_a_stride = 0; in_y = (const Pixel*) input->get_plane(heif_channel_Y, &in_y_stride); in_cb = (const Pixel*) input->get_plane(heif_channel_Cb, &in_cb_stride); @@ -883,14 +883,14 @@ Op_YCbCr422_bilinear_to_YCbCr444::convert_colorspace(const std::shared_pt // --- fill borders // left border - for (int cy = 0; cy < height; cy++) { + for (uint32_t cy = 0; cy < height; cy++) { out_cb[cy * out_cb_stride] = in_cb[cy * in_cb_stride]; out_cr[cy * out_cr_stride] = in_cr[cy * in_cr_stride]; } // right border if (width % 2 == 0) { - for (int cy = 0; cy < height; cy++) { + for (uint32_t cy = 0; cy < height; cy++) { out_cb[cy * out_cb_stride + width - 1] = in_cb[cy * in_cb_stride + width / 2 - 1]; out_cr[cy * out_cr_stride + width - 1] = in_cr[cy * in_cr_stride + width / 2 - 1]; } @@ -899,7 +899,7 @@ Op_YCbCr422_bilinear_to_YCbCr444::convert_colorspace(const std::shared_pt // --- bilinear filtering of inner part - int x, y; + uint32_t x, y; for (y = 0; y < height; y++) { for (x = 1; x < width - 1; x += 2) { int cx = x / 2; @@ -920,7 +920,7 @@ Op_YCbCr422_bilinear_to_YCbCr444::convert_colorspace(const std::shared_pt // TODO: check whether we can use HeifPixelImage::transfer_plane_from_image_as() instead of copying Y and Alpha for (y = 0; y < height; y++) { - int copyWidth = (hdr ? width * 2 : width); + uint32_t copyWidth = (hdr ? width * 2 : width); memcpy(&out_y[y * out_y_stride], &in_y[y * in_y_stride], copyWidth); diff --git a/libheif/color-conversion/colorconversion.cc b/libheif/color-conversion/colorconversion.cc index 412e0b8df7..364e4fb52b 100644 --- a/libheif/color-conversion/colorconversion.cc +++ b/libheif/color-conversion/colorconversion.cc @@ -283,7 +283,7 @@ bool ColorConversionPipeline::construct_pipeline(const ColorState& input_state, std::vector processed_states; std::vector border_states; - border_states.push_back({-1, nullptr, input_state, 0}); + border_states.emplace_back(-1, nullptr, input_state, 0); while (!border_states.empty()) { int minIdx = -1; @@ -402,10 +402,10 @@ bool ColorConversionPipeline::construct_pipeline(const ColorState& input_state, ColorStateWithCost s = out_state; s.speed_costs = s.speed_costs + processed_states.back().speed_costs; - border_states.push_back({(int) (processed_states.size() - 1), - op_ptr, - s.color_state, - s.speed_costs}); + border_states.emplace_back((int) (processed_states.size() - 1), + op_ptr, + s.color_state, + s.speed_costs); } } } @@ -488,8 +488,8 @@ std::shared_ptr convert_colorspace(const std::shared_ptrget_width(); - int height = input->get_height(); + uint32_t width = input->get_width(); + uint32_t height = input->get_height(); // alpha image should have full image resolution @@ -515,7 +515,7 @@ std::shared_ptr convert_colorspace(const std::shared_ptrget_colorspace(); input_state.chroma = input->get_chroma_format(); - input_state.has_alpha = input->has_channel(heif_channel_Alpha) || is_chroma_with_alpha(input->get_chroma_format()); + input_state.has_alpha = input->has_channel(heif_channel_Alpha) || is_interleaved_with_alpha(input->get_chroma_format()); if (input->get_color_profile_nclx()) { input_state.nclx_profile = *input->get_color_profile_nclx(); } @@ -552,7 +552,7 @@ std::shared_ptr convert_colorspace(const std::shared_ptr 1) { - output_state.has_alpha = is_chroma_with_alpha(target_chroma); + output_state.has_alpha = is_interleaved_with_alpha(target_chroma); } else { output_state.has_alpha = input_state.has_alpha; @@ -587,5 +587,23 @@ std::shared_ptr convert_colorspace(const std::shared_ptr convert_colorspace(const std::shared_ptr& input, + heif_colorspace colorspace, + heif_chroma chroma, + const std::shared_ptr& target_profile, + int output_bpp, + const heif_color_conversion_options& options) +{ + std::shared_ptr non_const_input = std::const_pointer_cast(input); + + return convert_colorspace(non_const_input, colorspace, chroma, target_profile, output_bpp, options); } diff --git a/libheif/color-conversion/colorconversion.h b/libheif/color-conversion/colorconversion.h index 969093e16d..6b53d815ef 100644 --- a/libheif/color-conversion/colorconversion.h +++ b/libheif/color-conversion/colorconversion.h @@ -96,12 +96,13 @@ class ColorConversionPipeline static void init_ops(); static void release_ops(); + bool is_nop() const { return m_conversion_steps.empty(); } + bool construct_pipeline(const ColorState& input_state, const ColorState& target_state, const heif_color_conversion_options& options); - std::shared_ptr - convert_image(const std::shared_ptr& input); + std::shared_ptr convert_image(const std::shared_ptr& input); std::string debug_dump_pipeline() const; @@ -120,6 +121,8 @@ class ColorConversionPipeline }; +// If no conversion is required, the input is simply passed through without copy. +// The input image is never modified by this function, but the input is still non-const because we may pass it through. std::shared_ptr convert_colorspace(const std::shared_ptr& input, heif_colorspace colorspace, heif_chroma chroma, @@ -127,4 +130,11 @@ std::shared_ptr convert_colorspace(const std::shared_ptr convert_colorspace(const std::shared_ptr& input, + heif_colorspace colorspace, + heif_chroma chroma, + const std::shared_ptr& target_profile, + int output_bpp, + const heif_color_conversion_options& options); + #endif diff --git a/libheif/color-conversion/hdr_sdr.cc b/libheif/color-conversion/hdr_sdr.cc index 5b697441f8..3e44e6e3dd 100644 --- a/libheif/color-conversion/hdr_sdr.cc +++ b/libheif/color-conversion/hdr_sdr.cc @@ -31,7 +31,7 @@ Op_to_hdr_planes::state_after_conversion(const ColorState& input_state, input_state.chroma != heif_chroma_420 && input_state.chroma != heif_chroma_422 && input_state.chroma != heif_chroma_444) || - input_state.bits_per_pixel != 8) { + input_state.bits_per_pixel != 8) { // TODO: support for <8 bpp return {}; } @@ -71,8 +71,8 @@ Op_to_hdr_planes::convert_colorspace(const std::shared_ptr heif_channel_B, heif_channel_Alpha}) { if (input->has_channel(channel)) { - int width = input->get_width(channel); - int height = input->get_height(channel); + uint32_t width = input->get_width(channel); + uint32_t height = input->get_height(channel); if (!outimg->add_plane(channel, width, height, target_state.bits_per_pixel)) { return nullptr; } @@ -84,17 +84,18 @@ Op_to_hdr_planes::convert_colorspace(const std::shared_ptr int shift2 = 2 * input_bits - output_bits; const uint8_t* p_in; - int stride_in; + uint32_t stride_in; p_in = input->get_plane(channel, &stride_in); uint16_t* p_out; - int stride_out; + uint32_t stride_out; p_out = (uint16_t*) outimg->get_plane(channel, &stride_out); stride_out /= 2; - for (int y = 0; y < height; y++) - for (int x = 0; x < width; x++) { + for (uint32_t y = 0; y < height; y++) + for (uint32_t x = 0; x < width; x++) { int in = p_in[y * stride_in + x]; + // TODO: support for <8 bpp may need more than two copies of the input bit pattern p_out[y * stride_out + x] = (uint16_t) ((in << shift1) | (in >> shift2)); } } @@ -125,7 +126,7 @@ Op_to_sdr_planes::state_after_conversion(const ColorState& input_state, ColorState output_state; - // --- decrease bit depth + // --- output bit depth = 8 output_state = input_state; output_state.bits_per_pixel = 8; @@ -161,8 +162,8 @@ Op_to_sdr_planes::convert_colorspace(const std::shared_ptr int input_bits = input->get_bits_per_pixel(channel); if (input_bits > 8) { - int width = input->get_width(channel); - int height = input->get_height(channel); + uint32_t width = input->get_width(channel); + uint32_t height = input->get_height(channel); if (!outimg->add_plane(channel, width, height, 8)) { return nullptr; } @@ -170,22 +171,22 @@ Op_to_sdr_planes::convert_colorspace(const std::shared_ptr int shift = input_bits - 8; const uint16_t* p_in; - int stride_in; + uint32_t stride_in; p_in = (uint16_t*) input->get_plane(channel, &stride_in); stride_in /= 2; uint8_t* p_out; - int stride_out; + uint32_t stride_out; p_out = outimg->get_plane(channel, &stride_out); - for (int y = 0; y < height; y++) - for (int x = 0; x < width; x++) { + for (uint32_t y = 0; y < height; y++) + for (uint32_t x = 0; x < width; x++) { int in = p_in[y * stride_in + x]; p_out[y * stride_out + x] = (uint8_t) (in >> shift); // TODO: I think no rounding here, but am not sure. } } else if (input_bits < 8) { - int width = input->get_width(channel); - int height = input->get_height(channel); + uint32_t width = input->get_width(channel); + uint32_t height = input->get_height(channel); if (!outimg->add_plane(channel, width, height, 8)) { return nullptr; } @@ -202,7 +203,7 @@ Op_to_sdr_planes::convert_colorspace(const std::shared_ptr // output assert(input_bits > 0 && input_bits < 8); - uint16_t bit = static_cast(1 << (16 - input_bits)); + auto bit = static_cast(1 << (16 - input_bits)); uint16_t mulFactor = bit; for (;;) { @@ -214,14 +215,14 @@ Op_to_sdr_planes::convert_colorspace(const std::shared_ptr mulFactor |= bit; } - int stride_in; + uint32_t stride_in; const uint8_t* p_in = input->get_plane(channel, &stride_in); - int stride_out; + uint32_t stride_out; uint8_t* p_out = outimg->get_plane(channel, &stride_out); - for (int y = 0; y < height; y++) - for (int x = 0; x < width; x++) { + for (uint32_t y = 0; y < height; y++) + for (uint32_t x = 0; x < width; x++) { int in = p_in[y * stride_in + x]; p_out[y * stride_out + x] = (uint8_t) ((in * mulFactor) >> 8); } diff --git a/libheif/color-conversion/monochrome.cc b/libheif/color-conversion/monochrome.cc index 24b2cb5e63..89ae6b59e0 100644 --- a/libheif/color-conversion/monochrome.cc +++ b/libheif/color-conversion/monochrome.cc @@ -57,15 +57,15 @@ Op_mono_to_YCbCr420::convert_colorspace(const std::shared_ptr(); - int width = input->get_width(); - int height = input->get_height(); + uint32_t width = input->get_width(); + uint32_t height = input->get_height(); outimg->create(width, height, heif_colorspace_YCbCr, heif_chroma_420); int input_bpp = input->get_bits_per_pixel(heif_channel_Y); - int chroma_width = (width + 1) / 2; - int chroma_height = (height + 1) / 2; + uint32_t chroma_width = (width + 1) / 2; + uint32_t chroma_height = (height + 1) / 2; if (!outimg->add_plane(heif_channel_Y, width, height, input_bpp) || !outimg->add_plane(heif_channel_Cb, chroma_width, chroma_height, input_bpp) || @@ -83,12 +83,12 @@ Op_mono_to_YCbCr420::convert_colorspace(const std::shared_ptrget_plane(heif_channel_Y, &in_y_stride); @@ -96,10 +96,12 @@ Op_mono_to_YCbCr420::convert_colorspace(const std::shared_ptrget_plane(heif_channel_Cb, &out_cb_stride); out_cr = outimg->get_plane(heif_channel_Cr, &out_cr_stride); - memset(out_cb, 128, out_cb_stride * chroma_height); - memset(out_cr, 128, out_cr_stride * chroma_height); + auto chroma_value = static_cast(1 << (input_bpp - 1)); - for (int y = 0; y < height; y++) { + memset(out_cb, chroma_value, out_cb_stride * chroma_height); + memset(out_cr, chroma_value, out_cr_stride * chroma_height); + + for (uint32_t y = 0; y < height; y++) { memcpy(out_y + y * out_y_stride, in_y + y * in_y_stride, width); @@ -107,10 +109,10 @@ Op_mono_to_YCbCr420::convert_colorspace(const std::shared_ptrget_plane(heif_channel_Y, &in_y_stride); @@ -123,13 +125,13 @@ Op_mono_to_YCbCr420::convert_colorspace(const std::shared_ptrget_plane(heif_channel_Alpha, &in_a_stride); out_a = outimg->get_plane(heif_channel_Alpha, &out_a_stride); - int memory_width = (alpha_bpp > 8 ? width * 2 : width); + uint32_t memory_width = (alpha_bpp > 8 ? width * 2 : width); - for (int y = 0; y < height; y++) { + for (uint32_t y = 0; y < height; y++) { memcpy(&out_a[y * out_a_stride], &in_a[y * in_a_stride], memory_width); } } @@ -204,8 +206,8 @@ Op_mono_to_RGB24_32::convert_colorspace(const std::shared_ptrget_width(); - int height = input->get_height(); + uint32_t width = input->get_width(); + uint32_t height = input->get_height(); if (input->get_bits_per_pixel(heif_channel_Y) != 8) { return nullptr; @@ -227,10 +229,10 @@ Op_mono_to_RGB24_32::convert_colorspace(const std::shared_ptrget_plane(heif_channel_Y, &in_y_stride); if (has_alpha) { @@ -239,7 +241,7 @@ Op_mono_to_RGB24_32::convert_colorspace(const std::shared_ptrget_plane(heif_channel_interleaved, &out_p_stride); - int x, y; + uint32_t x, y; for (y = 0; y < height; y++) { if (target_state.has_alpha == false) { for (x = 0; x < width; x++) { diff --git a/libheif/color-conversion/rgb2rgb.cc b/libheif/color-conversion/rgb2rgb.cc index d8338bbb8c..94c6136370 100644 --- a/libheif/color-conversion/rgb2rgb.cc +++ b/libheif/color-conversion/rgb2rgb.cc @@ -84,8 +84,8 @@ Op_RGB_to_RGB24_32::convert_colorspace(const std::shared_ptr(); - int width = input->get_width(); - int height = input->get_height(); + uint32_t width = input->get_width(); + uint32_t height = input->get_height(); outimg->create(width, height, heif_colorspace_RGB, want_alpha ? heif_chroma_interleaved_32bit : heif_chroma_interleaved_24bit); @@ -95,10 +95,10 @@ Op_RGB_to_RGB24_32::convert_colorspace(const std::shared_ptrget_plane(heif_channel_R, &in_r_stride); in_g = input->get_plane(heif_channel_G, &in_g_stride); @@ -109,7 +109,7 @@ Op_RGB_to_RGB24_32::convert_colorspace(const std::shared_ptrget_plane(heif_channel_Alpha, &in_a_stride); } - int x, y; + uint32_t x, y; for (y = 0; y < height; y++) { if (has_alpha && want_alpha) { @@ -152,7 +152,7 @@ Op_RGB_HDR_to_RRGGBBaa_BE::state_after_conversion(const ColorState& input_state, if (input_state.colorspace != heif_colorspace_RGB || input_state.chroma != heif_chroma_444 || - input_state.bits_per_pixel == 8) { + input_state.bits_per_pixel <= 8) { return {}; } @@ -192,9 +192,9 @@ Op_RGB_HDR_to_RRGGBBaa_BE::convert_colorspace(const std::shared_ptrget_bits_per_pixel(heif_channel_R) == 8 || - input->get_bits_per_pixel(heif_channel_G) == 8 || - input->get_bits_per_pixel(heif_channel_B) == 8) { + if (input->get_bits_per_pixel(heif_channel_R) <= 8 || + input->get_bits_per_pixel(heif_channel_G) <= 8 || + input->get_bits_per_pixel(heif_channel_B) <= 8) { return nullptr; } @@ -202,7 +202,7 @@ Op_RGB_HDR_to_RRGGBBaa_BE::convert_colorspace(const std::shared_ptrget_bits_per_pixel(heif_channel_Alpha) == 8) { + if (input->get_bits_per_pixel(heif_channel_Alpha) <= 8) { return nullptr; } @@ -216,8 +216,8 @@ Op_RGB_HDR_to_RRGGBBaa_BE::convert_colorspace(const std::shared_ptr(); - int width = input->get_width(); - int height = input->get_height(); + uint32_t width = input->get_width(); + uint32_t height = input->get_height(); outimg->create(width, height, heif_colorspace_RGB, output_has_alpha ? heif_chroma_interleaved_RRGGBBAA_BE : heif_chroma_interleaved_RRGGBB_BE); @@ -226,10 +226,10 @@ Op_RGB_HDR_to_RRGGBBaa_BE::convert_colorspace(const std::shared_ptrget_plane(heif_channel_R, &in_r_stride); in_g = (uint16_t*) input->get_plane(heif_channel_G, &in_g_stride); @@ -238,6 +238,12 @@ Op_RGB_HDR_to_RRGGBBaa_BE::convert_colorspace(const std::shared_ptrget_plane(heif_channel_Alpha, &in_a_stride); + assert(in_a != nullptr); + + // should never happen, but makes clang-tidy happy + if (in_a == nullptr) { + return nullptr; + } } in_r_stride /= 2; @@ -247,8 +253,8 @@ Op_RGB_HDR_to_RRGGBBaa_BE::convert_colorspace(const std::shared_ptr((1 << bpp) - 1); + uint32_t x, y; + auto alpha_max = static_cast((1 << bpp) - 1); for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { uint16_t r = in_r[x + y * in_r_stride]; @@ -338,8 +344,8 @@ Op_RGB_to_RRGGBBaa_BE::convert_colorspace(const std::shared_ptr(); - int width = input->get_width(); - int height = input->get_height(); + uint32_t width = input->get_width(); + uint32_t height = input->get_height(); outimg->create(width, height, heif_colorspace_RGB, output_has_alpha ? heif_chroma_interleaved_RRGGBBAA_BE : heif_chroma_interleaved_RRGGBB_BE); @@ -349,10 +355,10 @@ Op_RGB_to_RRGGBBaa_BE::convert_colorspace(const std::shared_ptrget_plane(heif_channel_R, &in_r_stride); in_g = input->get_plane(heif_channel_G, &in_g_stride); @@ -365,7 +371,7 @@ Op_RGB_to_RRGGBBaa_BE::convert_colorspace(const std::shared_ptr(); - int width = input->get_width(); - int height = input->get_height(); + uint32_t width = input->get_width(); + uint32_t height = input->get_height(); int bpp = input->get_bits_per_pixel(heif_channel_interleaved); outimg->create(width, height, heif_colorspace_RGB, heif_chroma_444); @@ -464,11 +470,11 @@ Op_RRGGBBaa_BE_to_RGB_HDR::convert_colorspace(const std::shared_ptrget_plane(heif_channel_interleaved, &in_p_stride); @@ -485,8 +491,8 @@ Op_RRGGBBaa_BE_to_RGB_HDR::convert_colorspace(const std::shared_ptr((1 << bpp) - 1); - int x, y; + auto alpha_max = static_cast((1 << bpp) - 1); + uint32_t x, y; for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { @@ -557,8 +563,8 @@ Op_RGB24_32_to_RGB::convert_colorspace(const std::shared_ptr(); - int width = input->get_width(); - int height = input->get_height(); + uint32_t width = input->get_width(); + uint32_t height = input->get_height(); outimg->create(width, height, heif_colorspace_RGB, heif_chroma_444); @@ -575,11 +581,11 @@ Op_RGB24_32_to_RGB::convert_colorspace(const std::shared_ptrget_plane(heif_channel_interleaved, &in_p_stride); @@ -591,7 +597,7 @@ Op_RGB24_32_to_RGB::convert_colorspace(const std::shared_ptrget_plane(heif_channel_Alpha, &out_a_stride); } - int x, y; + uint32_t x, y; for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { @@ -681,8 +687,8 @@ Op_RRGGBBaa_swap_endianness::convert_colorspace(const std::shared_ptr(); - int width = input->get_width(); - int height = input->get_height(); + uint32_t width = input->get_width(); + uint32_t height = input->get_height(); switch (input->get_chroma_format()) { case heif_chroma_interleaved_RRGGBB_LE: @@ -707,17 +713,17 @@ Op_RRGGBBaa_swap_endianness::convert_colorspace(const std::shared_ptrget_plane(heif_channel_interleaved, &in_p_stride); out_p = outimg->get_plane(heif_channel_interleaved, &out_p_stride); - int n_bytes = std::min(in_p_stride, out_p_stride); + uint32_t n_bytes = std::min(in_p_stride, out_p_stride); - int x, y; + uint32_t x, y; for (y = 0; y < height; y++) { for (x = 0; x < n_bytes; x += 2) { out_p[y * out_p_stride + x + 0] = in_p[y * in_p_stride + x + 1]; diff --git a/libheif/color-conversion/rgb2yuv.cc b/libheif/color-conversion/rgb2yuv.cc index 0be8042e51..9c96ecb17e 100644 --- a/libheif/color-conversion/rgb2yuv.cc +++ b/libheif/color-conversion/rgb2yuv.cc @@ -35,7 +35,12 @@ Op_RGB_to_YCbCr::state_after_conversion(const ColorState& input_state, { bool hdr = !std::is_same::value; - if ((input_state.bits_per_pixel != 8) != hdr) { + if ((input_state.bits_per_pixel > 8) != hdr) { + return {}; + } + + // TODO: add support for <8 bpp + if (input_state.bits_per_pixel < 8) { return {}; } @@ -94,15 +99,15 @@ Op_RGB_to_YCbCr::convert_colorspace(const std::shared_ptr::value; - int width = input->get_width(); - int height = input->get_height(); + uint32_t width = input->get_width(); + uint32_t height = input->get_height(); heif_chroma chroma = target_state.chroma; int subH = chroma_h_subsampling(chroma); int subV = chroma_v_subsampling(chroma); int bpp = input->get_bits_per_pixel(heif_channel_R); - if ((bpp != 8) != hdr) { + if (bpp < 8 || (bpp > 8) != hdr) { return nullptr; } @@ -116,8 +121,8 @@ Op_RGB_to_YCbCr::convert_colorspace(const std::shared_ptrcreate(width, height, heif_colorspace_YCbCr, chroma); - int cwidth = (width + subH - 1) / subH; - int cheight = (height + subV - 1) / subV; + uint32_t cwidth = (width + subH - 1) / subH; + uint32_t cheight = (height + subV - 1) / subV; if (!outimg->add_plane(heif_channel_Y, width, height, bpp) || !outimg->add_plane(heif_channel_Cb, cwidth, cheight, bpp) || @@ -132,10 +137,10 @@ Op_RGB_to_YCbCr::convert_colorspace(const std::shared_ptrget_plane(heif_channel_R, &in_r_stride); in_g = (const Pixel*) input->get_plane(heif_channel_G, &in_g_stride); @@ -176,7 +181,7 @@ Op_RGB_to_YCbCr::convert_colorspace(const std::shared_ptr::convert_colorspace(const std::shared_ptr 1 || subV > 1) { - int x2 = (x + 1 < width && subH == 2 && subV == 2) ? x + 1 : x; // subV==2 -> Do not center for 4:2:2 (see comment in Op_RGB24_32_to_YCbCr, github issue #521) - int y2 = (y + 1 < height && subV == 2) ? y + 1 : y; + uint32_t x2 = (x + 1 < width && subH == 2 && subV == 2) ? x + 1 : x; // subV==2 -> Do not center for 4:2:2 (see comment in Op_RGB24_32_to_YCbCr, github issue #521) + uint32_t y2 = (y + 1 < height && subV == 2) ? y + 1 : y; r += in_r[y * in_r_stride + x2]; g += in_g[y * in_g_stride + x2]; @@ -296,7 +301,7 @@ Op_RRGGBBxx_HDR_to_YCbCr420::state_after_conversion(const ColorState& input_stat input_state.chroma == heif_chroma_interleaved_RRGGBB_LE || input_state.chroma == heif_chroma_interleaved_RRGGBBAA_BE || input_state.chroma == heif_chroma_interleaved_RRGGBBAA_LE) || - input_state.bits_per_pixel == 8) { + input_state.bits_per_pixel <= 8) { return {}; } @@ -337,8 +342,8 @@ Op_RRGGBBxx_HDR_to_YCbCr420::convert_colorspace(const std::shared_ptrget_width(); - int height = input->get_height(); + uint32_t width = input->get_width(); + uint32_t height = input->get_height(); int bpp = input->get_bits_per_pixel(heif_channel_interleaved); @@ -351,8 +356,8 @@ Op_RRGGBBxx_HDR_to_YCbCr420::convert_colorspace(const std::shared_ptradd_plane(heif_channel_Y, width, height, bpp) || !outimg->add_plane(heif_channel_Cb, cwidth, cheight, bpp) || @@ -367,10 +372,10 @@ Op_RRGGBBxx_HDR_to_YCbCr420::convert_colorspace(const std::shared_ptrget_plane(heif_channel_interleaved, &in_p_stride); out_y = (uint16_t*) outimg->get_plane(heif_channel_Y, &out_y_stride); @@ -401,8 +406,8 @@ Op_RRGGBBxx_HDR_to_YCbCr420::convert_colorspace(const std::shared_ptr((in[0 + le] << 8) | in[1 - le]); @@ -543,8 +548,8 @@ Op_RGB24_32_to_YCbCr::convert_colorspace(const std::shared_ptrget_width(); - int height = input->get_height(); + uint32_t width = input->get_width(); + uint32_t height = input->get_height(); auto outimg = std::make_shared(); @@ -573,10 +578,10 @@ Op_RGB24_32_to_YCbCr::convert_colorspace(const std::shared_ptrget_plane(heif_channel_interleaved, &in_stride); @@ -601,10 +606,10 @@ Op_RGB24_32_to_YCbCr::convert_colorspace(const std::shared_ptrget_width(); - int height = input->get_height(); + uint32_t width = input->get_width(); + uint32_t height = input->get_height(); auto outimg = std::make_shared(); @@ -837,11 +842,11 @@ Op_RGB24_32_to_YCbCr444_GBR::convert_colorspace(const std::shared_ptrget_plane(heif_channel_interleaved, &in_stride); @@ -857,8 +862,8 @@ Op_RGB24_32_to_YCbCr444_GBR::convert_colorspace(const std::shared_ptrget_width(); - int height = input->get_height(); + uint32_t width = input->get_width(); + uint32_t height = input->get_height(); auto outimg = std::make_shared(); @@ -142,8 +142,8 @@ Op_Any_RGB_to_YCbCr_420_Sharp::convert_colorspace( outimg->create(width, height, heif_colorspace_YCbCr, output_chroma); - int chroma_width = (width + chromaSubH - 1) / chromaSubH; - int chroma_height = (height + chromaSubV - 1) / chromaSubV; + uint32_t chroma_width = (width + chromaSubH - 1) / chromaSubH; + uint32_t chroma_height = (height + chromaSubV - 1) / chromaSubV; bool has_alpha = input->get_chroma_format() == heif_chroma_interleaved_RGBA || @@ -175,12 +175,12 @@ Op_Any_RGB_to_YCbCr_420_Sharp::convert_colorspace( : 2; const uint8_t* in_r, * in_g, * in_b, * in_a = nullptr; - int in_stride = 0; - int in_a_stride = 0; + uint32_t in_stride = 0; + uint32_t in_a_stride = 0; bool planar_input = input_chroma == heif_chroma_444; int input_bits = 0; if (planar_input) { - int in_r_stride = 0, in_g_stride = 0, in_b_stride = 0; + uint32_t in_r_stride = 0, in_g_stride = 0, in_b_stride = 0; in_r = input->get_plane(heif_channel_R, &in_r_stride); in_g = input->get_plane(heif_channel_G, &in_g_stride); in_b = input->get_plane(heif_channel_B, &in_b_stride); @@ -211,7 +211,7 @@ Op_Any_RGB_to_YCbCr_420_Sharp::convert_colorspace( } } - int out_cb_stride = 0, out_cr_stride = 0, out_y_stride = 0; + uint32_t out_cb_stride = 0, out_cr_stride = 0, out_y_stride = 0; uint8_t* out_y = outimg->get_plane(heif_channel_Y, &out_y_stride); uint8_t* out_cb = outimg->get_plane(heif_channel_Cb, &out_cb_stride); uint8_t* out_cr = outimg->get_plane(heif_channel_Cr, &out_cr_stride); @@ -246,12 +246,12 @@ Op_Any_RGB_to_YCbCr_420_Sharp::convert_colorspace( (planar_input && !PlatformIsBigEndian())) ? 1 : 0; - int out_a_stride; + uint32_t out_a_stride; uint8_t* out_a = outimg->get_plane(heif_channel_Alpha, &out_a_stride); uint16_t alpha_max = static_cast((1 << input_bits) - 1); - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { + for (uint32_t y = 0; y < height; y++) { + for (uint32_t x = 0; x < width; x++) { const uint8_t* in = has_alpha ? &in_a[y * in_a_stride + x * rgb_step] : nullptr; uint16_t a = has_alpha ? ((input_bits == 8) diff --git a/libheif/color-conversion/yuv2rgb.cc b/libheif/color-conversion/yuv2rgb.cc index 06f7ae756c..de27b5930b 100644 --- a/libheif/color-conversion/yuv2rgb.cc +++ b/libheif/color-conversion/yuv2rgb.cc @@ -55,7 +55,12 @@ Op_YCbCr_to_RGB::state_after_conversion(const ColorState& input_state, bool hdr = !std::is_same::value; - if ((input_state.bits_per_pixel != 8) != hdr) { + if ((input_state.bits_per_pixel > 8) != hdr) { + return {}; + } + + // TODO: add support for <8 bpp + if (input_state.bits_per_pixel < 8) { return {}; } @@ -123,8 +128,8 @@ Op_YCbCr_to_RGB::convert_colorspace(const std::shared_ptrget_color_profile_nclx(); - int width = input->get_width(); - int height = input->get_height(); + uint32_t width = input->get_width(); + uint32_t height = input->get_height(); auto outimg = std::make_shared(); @@ -143,10 +148,10 @@ Op_YCbCr_to_RGB::convert_colorspace(const std::shared_ptrget_plane(heif_channel_Y, &in_y_stride); in_cb = (const Pixel*) input->get_plane(heif_channel_Cb, &in_cb_stride); @@ -195,7 +200,7 @@ Op_YCbCr_to_RGB::convert_colorspace(const std::shared_ptr> shiftH); @@ -317,8 +322,8 @@ Op_YCbCr420_to_RGB24::convert_colorspace(const std::shared_ptr(); - int width = input->get_width(); - int height = input->get_height(); + uint32_t width = input->get_width(); + uint32_t height = input->get_height(); outimg->create(width, height, heif_colorspace_RGB, heif_chroma_interleaved_24bit); @@ -339,17 +344,17 @@ Op_YCbCr420_to_RGB24::convert_colorspace(const std::shared_ptr(std::lround(256 * coeffs.b_cb)); const uint8_t* in_y, * in_cb, * in_cr; - int in_y_stride = 0, in_cb_stride = 0, in_cr_stride = 0; + uint32_t in_y_stride = 0, in_cb_stride = 0, in_cr_stride = 0; uint8_t* out_p; - int out_p_stride = 0; + uint32_t out_p_stride = 0; in_y = input->get_plane(heif_channel_Y, &in_y_stride); in_cb = input->get_plane(heif_channel_Cb, &in_cb_stride); in_cr = input->get_plane(heif_channel_Cr, &in_cr_stride); out_p = outimg->get_plane(heif_channel_interleaved, &out_p_stride); - int x, y; + uint32_t x, y; for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { int yv = (in_y[y * in_y_stride + x]); @@ -427,8 +432,8 @@ Op_YCbCr420_to_RGB32::convert_colorspace(const std::shared_ptr(); - int width = input->get_width(); - int height = input->get_height(); + uint32_t width = input->get_width(); + uint32_t height = input->get_height(); outimg->create(width, height, heif_colorspace_RGB, heif_chroma_interleaved_32bit); @@ -455,10 +460,10 @@ Op_YCbCr420_to_RGB32::convert_colorspace(const std::shared_ptrhas_channel(heif_channel_Alpha); const uint8_t* in_y, * in_cb, * in_cr, * in_a = nullptr; - int in_y_stride = 0, in_cb_stride = 0, in_cr_stride = 0, in_a_stride = 0; + uint32_t in_y_stride = 0, in_cb_stride = 0, in_cr_stride = 0, in_a_stride = 0; uint8_t* out_p; - int out_p_stride = 0; + uint32_t out_p_stride = 0; in_y = input->get_plane(heif_channel_Y, &in_y_stride); in_cb = input->get_plane(heif_channel_Cb, &in_cb_stride); @@ -469,7 +474,7 @@ Op_YCbCr420_to_RGB32::convert_colorspace(const std::shared_ptrget_plane(heif_channel_interleaved, &out_p_stride); - int x, y; + uint32_t x, y; for (y = 0; y < height; y++) { for (x = 0; x < width; x++) { @@ -511,7 +516,7 @@ Op_YCbCr420_to_RRGGBBaa::state_after_conversion(const ColorState& input_state, if (input_state.colorspace != heif_colorspace_YCbCr || input_state.chroma != heif_chroma_420 || - input_state.bits_per_pixel == 8) { + input_state.bits_per_pixel <= 8) { return {}; } @@ -553,8 +558,8 @@ Op_YCbCr420_to_RRGGBBaa::convert_colorspace(const std::shared_ptrget_width(); - int height = input->get_height(); + uint32_t width = input->get_width(); + uint32_t height = input->get_height(); int bpp = input->get_bits_per_pixel(heif_channel_Y); bool has_alpha = input->has_channel(heif_channel_Alpha); @@ -578,10 +583,10 @@ Op_YCbCr420_to_RRGGBBaa::convert_colorspace(const std::shared_ptrget_plane(heif_channel_interleaved, &out_p_stride); in_y = (uint16_t*) input->get_plane(heif_channel_Y, &in_y_stride); @@ -606,8 +611,8 @@ Op_YCbCr420_to_RRGGBBaa::convert_colorspace(const std::shared_ptr(16 << (bpp - 8)); - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { + for (uint32_t y = 0; y < height; y++) { + for (uint32_t x = 0; x < width; x++) { float y_ = in_y[y * in_y_stride / 2 + x]; float cb = static_cast(in_cb[y / 2 * in_cb_stride / 2 + x / 2] - (1 << (bpp - 1))); diff --git a/libheif/common_utils.cc b/libheif/common_utils.cc index 48c6ce2182..2a204e49ac 100644 --- a/libheif/common_utils.cc +++ b/libheif/common_utils.cc @@ -62,10 +62,10 @@ uint8_t chroma_v_subsampling(heif_chroma c) } -void get_subsampled_size(int width, int height, - heif_channel channel, - heif_chroma chroma, - int* subsampled_width, int* subsampled_height) +void get_subsampled_size(uint32_t width, uint32_t height, + heif_channel channel, + heif_chroma chroma, + uint32_t* subsampled_width, uint32_t* subsampled_height) { if (channel == heif_channel_Cb || channel == heif_channel_Cr) { @@ -100,3 +100,15 @@ uint8_t compute_avif_profile(int bits_per_pixel, heif_chroma chroma) return 2; } } + + +std::string fourcc_to_string(uint32_t code) +{ + std::string str(" "); + str[0] = static_cast((code >> 24) & 0xFF); + str[1] = static_cast((code >> 16) & 0xFF); + str[2] = static_cast((code >> 8) & 0xFF); + str[3] = static_cast((code >> 0) & 0xFF); + + return str; +} diff --git a/libheif/common_utils.h b/libheif/common_utils.h index f7f4a5f5ab..180880ae29 100644 --- a/libheif/common_utils.h +++ b/libheif/common_utils.h @@ -23,6 +23,7 @@ #include #include "libheif/heif.h" +#include #ifdef _MSC_VER #define MAYBE_UNUSED @@ -31,7 +32,7 @@ #endif -constexpr inline uint32_t fourcc_to_uint32(const char* id) +constexpr inline uint32_t fourcc(const char* id) { return (((((uint32_t) id[0])&0xFF) << 24) | ((((uint32_t) id[1])&0xFF) << 16) | @@ -39,6 +40,8 @@ constexpr inline uint32_t fourcc_to_uint32(const char* id) ((((uint32_t) id[3])&0xFF) << 0)); } +std::string fourcc_to_string(uint32_t code); + // Functions for common use in libheif and the plugins. @@ -46,10 +49,10 @@ uint8_t chroma_h_subsampling(heif_chroma c); uint8_t chroma_v_subsampling(heif_chroma c); -void get_subsampled_size(int width, int height, +void get_subsampled_size(uint32_t width, uint32_t height, heif_channel channel, heif_chroma chroma, - int* subsampled_width, int* subsampled_height); + uint32_t* subsampled_width, uint32_t* subsampled_height); uint8_t compute_avif_profile(int bits_per_pixel, heif_chroma chroma); diff --git a/libheif/compression.h b/libheif/compression.h new file mode 100644 index 0000000000..085a71cc6c --- /dev/null +++ b/libheif/compression.h @@ -0,0 +1,97 @@ +/* + * 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_COMPRESSION_H +#define LIBHEIF_COMPRESSION_H + +#include +#include +#include + +#include + +#if HAVE_ZLIB +/** + * Compress data using zlib method. + * + * This is the RFC 1950 format. + * + * @param input pointer to the data to be compressed + * @param size the length of the input array in bytes + * @return the corresponding compressed data + */ +std::vector compress_zlib(const uint8_t* input, size_t size); + +/** + * Compress data using deflate method. + * + * This is the RFC 1951 format. + * + * @param input pointer to the data to be compressed + * @param size the length of the input array in bytes + * @return the corresponding compressed data + */ +std::vector compress_deflate(const uint8_t* input, size_t size); + +/** + * Decompress zlib compressed data. + * + * This is assumed to be in RFC 1950 format, which is the normal zlib format. + * + * @param compressed_input the compressed data to be decompressed + * @param output pointer to the resulting vector of decompressed data + * @return success (Ok) or an error on failure (usually corrupt data) + * + * @sa decompress_deflate + * @sa compress_zlib + */ +Error decompress_zlib(const std::vector& compressed_input, std::vector* output); + +/** + * Decompress "deflate" compressed data. + * + * This is assumed to be in RFC 1951 format, which is the deflate format. + * + * @param compressed_input the compressed data to be decompressed + * @param output pointer to the resulting vector of decompressed data + * @return success (Ok) or an error on failure (usually corrupt data) + * + * @sa decompress_zlib + * @sa compress_deflate + */ +Error decompress_deflate(const std::vector& compressed_input, std::vector* output); + +#endif + +#if HAVE_BROTLI +/** + * Decompress Brotli compressed data. + * + * Brotli is described at https://brotli.org/ + * + * @param compressed_input the compressed data to be decompressed + * @param output pointer to the resulting vector of decompressed data + * @return success (Ok) or an error on failure (usually corrupt data) + */ +Error decompress_brotli(const std::vector& compressed_input, std::vector* output); + +std::vector compress_brotli(const uint8_t* input, size_t size); +#endif + +#endif //LIBHEIF_COMPRESSION_H diff --git a/libheif/compression_brotli.cc b/libheif/compression_brotli.cc new file mode 100644 index 0000000000..c3a7f7bb2d --- /dev/null +++ b/libheif/compression_brotli.cc @@ -0,0 +1,127 @@ +/* + * HEIF codec. + * Copyright (c) 2024 Brad Hards + * + * 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 "compression.h" + +#if HAVE_BROTLI + +const size_t BUF_SIZE = (1 << 18); +#include +#include +#include +#include +#include +#include + +#include "error.h" + + +Error decompress_brotli(const std::vector &compressed_input, std::vector *output) +{ + BrotliDecoderResult result = BROTLI_DECODER_RESULT_ERROR; + std::vector buffer(BUF_SIZE, 0); + size_t available_in = compressed_input.size(); + const std::uint8_t *next_in = reinterpret_cast(compressed_input.data()); + size_t available_out = buffer.size(); + std::uint8_t *next_output = buffer.data(); + + std::unique_ptr state(BrotliDecoderCreateInstance(0, 0, 0), BrotliDecoderDestroyInstance); + + while (true) + { + result = BrotliDecoderDecompressStream(state.get(), &available_in, &next_in, &available_out, &next_output, 0); + + if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_OUTPUT) + { + output->insert(output->end(), buffer.data(), buffer.data() + std::distance(buffer.data(), next_output)); + available_out = buffer.size(); + next_output = buffer.data(); + } + else if (result == BROTLI_DECODER_RESULT_SUCCESS) + { + output->insert(output->end(), buffer.data(), buffer.data() + std::distance(buffer.data(), next_output)); + break; + } + else if (result == BROTLI_DECODER_RESULT_NEEDS_MORE_INPUT) + { + std::stringstream sstr; + sstr << "Error performing brotli inflate - insufficient data.\n"; + return Error(heif_error_Invalid_input, heif_suberror_Decompression_invalid_data, sstr.str()); + } + else if (result == BROTLI_DECODER_RESULT_ERROR) + { + const char* errorMessage = BrotliDecoderErrorString(BrotliDecoderGetErrorCode(state.get())); + std::stringstream sstr; + sstr << "Error performing brotli inflate - " << errorMessage << "\n"; + return Error(heif_error_Invalid_input, heif_suberror_Decompression_invalid_data, sstr.str()); + } + else + { + const char* errorMessage = BrotliDecoderErrorString(BrotliDecoderGetErrorCode(state.get())); + std::stringstream sstr; + sstr << "Unknown error performing brotli inflate - " << errorMessage << "\n"; + return Error(heif_error_Invalid_input, heif_suberror_Decompression_invalid_data, sstr.str()); + } + } + + return Error::Ok; +} + + +std::vector compress_brotli(const uint8_t* input, size_t size) +{ + std::unique_ptr state(BrotliEncoderCreateInstance(nullptr, nullptr, nullptr), BrotliEncoderDestroyInstance); + + size_t available_in = size; + const uint8_t* next_in = input; + + std::vector tmp(BUF_SIZE); + size_t available_out = BUF_SIZE; + uint8_t* next_out = tmp.data(); + + std::vector result; + + for (;;) { + BROTLI_BOOL success = BrotliEncoderCompressStream(state.get(), + BROTLI_OPERATION_FINISH, + &available_in, + &next_in, + &available_out, + &next_out, + nullptr); + if (!success) { + return {}; + } + + if (next_out != tmp.data()) { + result.insert(result.end(), tmp.data(), tmp.data() + std::distance(tmp.data(), next_out)); + available_out = BUF_SIZE; + next_out = tmp.data(); + } + + if (BrotliEncoderIsFinished(state.get())) { + break; + } + } + + return result; +} + +#endif diff --git a/libheif/metadata_compression.cc b/libheif/compression_zlib.cc similarity index 63% rename from libheif/metadata_compression.cc rename to libheif/compression_zlib.cc index b738d1f62d..18f1158c27 100644 --- a/libheif/metadata_compression.cc +++ b/libheif/compression_zlib.cc @@ -19,14 +19,16 @@ */ -#include "metadata_compression.h" +#include "compression.h" -#if WITH_DEFLATE_HEADER_COMPRESSION +#if HAVE_ZLIB + #include #include +#include -std::vector deflate(const uint8_t* input, size_t size) +std::vector compress(const uint8_t* input, size_t size, int windowSize) { std::vector output; @@ -48,7 +50,7 @@ std::vector deflate(const uint8_t* input, size_t size) strm.zfree = Z_NULL; strm.opaque = Z_NULL; - int err = deflateInit(&strm, Z_DEFAULT_COMPRESSION); + int err = deflateInit2(&strm, Z_DEFAULT_COMPRESSION, Z_DEFLATED, windowSize, 8, Z_DEFAULT_STRATEGY); if (err != Z_OK) { return {}; // TODO: return error } @@ -78,9 +80,8 @@ std::vector deflate(const uint8_t* input, size_t size) } -std::vector inflate(const std::vector& compressed_input) +Error do_inflate(const std::vector& compressed_input, int windowSize, std::vector *output) { - std::vector output; // decompress data with zlib @@ -102,10 +103,11 @@ std::vector inflate(const std::vector& compressed_input) int err = -1; - err = inflateInit(&strm); + err = inflateInit2(&strm, windowSize); if (err != Z_OK) { - // TODO: return error - return {}; + std::stringstream sstr; + sstr << "Error initialising zlib inflate: " << strm.msg << " (" << err << ")\n"; + return Error(heif_error_Memory_allocation_error, heif_suberror_Compression_initialisation_error, sstr.str()); } do { @@ -118,19 +120,40 @@ std::vector inflate(const std::vector& compressed_input) // -> do nothing } else if (err == Z_NEED_DICT || err == Z_DATA_ERROR || err == Z_STREAM_ERROR) { - // TODO: return error - return {}; + inflateEnd(&strm); + std::stringstream sstr; + sstr << "Error performing zlib inflate: " << strm.msg << " (" << err << ")\n"; + return Error(heif_error_Invalid_input, heif_suberror_Decompression_invalid_data, sstr.str()); } - // append decoded data to output - - output.insert(output.end(), dst, dst + outBufferSize - strm.avail_out); + output->insert(output->end(), dst, dst + outBufferSize - strm.avail_out); } while (err != Z_STREAM_END); inflateEnd(&strm); - return output; + return Error::Ok; +} + +std::vector compress_zlib(const uint8_t* input, size_t size) +{ + return compress(input, size, 15); +} + +std::vector compress_deflate(const uint8_t* input, size_t size) +{ + return compress(input, size, -15); +} + + +Error decompress_zlib(const std::vector& compressed_input, std::vector *output) +{ + return do_inflate(compressed_input, 15, output); +} + +Error decompress_deflate(const std::vector& compressed_input, std::vector *output) +{ + return do_inflate(compressed_input, -15, output); } #endif diff --git a/libheif/context.cc b/libheif/context.cc index b123e86d8c..67bb6c4ff9 100644 --- a/libheif/context.cc +++ b/libheif/context.cc @@ -30,6 +30,8 @@ #include #include #include +#include "image-items/image_item.h" +#include #if ENABLE_PARALLEL_TILE_DECODING #include @@ -40,18 +42,21 @@ #include "pixelimage.h" #include "libheif/api_structs.h" #include "security_limits.h" -#include "metadata_compression.h" +#include "compression.h" #include "color-conversion/colorconversion.h" #include "plugin_registry.h" -#include "codecs/hevc.h" -#include "codecs/vvc.h" -#include "codecs/avif.h" -#include "codecs/jpeg.h" -#include "codecs/mask_image.h" -#include "codecs/jpeg2000.h" +#include "image-items/hevc.h" +#include "image-items/vvc.h" +#include "image-items/avif.h" +#include "image-items/jpeg.h" +#include "image-items/mask_image.h" +#include "image-items/jpeg2000.h" +#include "image-items/grid.h" +#include "image-items/overlay.h" +#include "image-items/tiled.h" #if WITH_UNCOMPRESSED_CODEC -#include "codecs/uncompressed_image.h" +#include "image-items/unc_image.h" #endif @@ -83,340 +88,56 @@ struct heif_error heif_encoder::alloc() return error; } - struct heif_error err = {heif_error_Ok, heif_suberror_Unspecified, kSuccess}; + struct heif_error err = {heif_error_Ok, heif_suberror_Unspecified, Error::kSuccess}; return err; } -static int32_t readvec_signed(const std::vector& data, int& ptr, int len) -{ - const uint32_t high_bit = 0x80 << ((len - 1) * 8); - - uint32_t val = 0; - while (len--) { - val <<= 8; - val |= data[ptr++]; - } - - bool negative = (val & high_bit) != 0; - val &= ~high_bit; - - if (negative) { - return -(high_bit - val); - } - else { - return val; - } - - return val; -} - - -static uint32_t readvec(const std::vector& data, int& ptr, int len) -{ - uint32_t val = 0; - while (len--) { - val <<= 8; - val |= data[ptr++]; - } - - return val; -} - - -class ImageGrid -{ -public: - Error parse(const std::vector& data); - - std::vector write() const; - - std::string dump() const; - - uint32_t get_width() const { return m_output_width; } - - uint32_t get_height() const { return m_output_height; } - - uint16_t get_rows() const - { - assert(m_rows <= 256); - return m_rows; - } - - uint16_t get_columns() const - { - assert(m_columns <= 256); - return m_columns; - } - - void set_num_tiles(uint16_t columns, uint16_t rows) - { - m_rows = rows; - m_columns = columns; - } - - void set_output_size(uint32_t width, uint32_t height) - { - m_output_width = width; - m_output_height = height; - } - -private: - uint16_t m_rows = 0; - uint16_t m_columns = 0; - uint32_t m_output_width = 0; - uint32_t m_output_height = 0; -}; - - -Error ImageGrid::parse(const std::vector& data) -{ - if (data.size() < 8) { - return Error(heif_error_Invalid_input, - heif_suberror_Invalid_grid_data, - "Less than 8 bytes of data"); - } - - uint8_t version = data[0]; - (void) version; // version is unused - - uint8_t flags = data[1]; - int field_size = ((flags & 1) ? 32 : 16); - - m_rows = static_cast(data[2] + 1); - m_columns = static_cast(data[3] + 1); - - if (field_size == 32) { - if (data.size() < 12) { - return Error(heif_error_Invalid_input, - heif_suberror_Invalid_grid_data, - "Grid image data incomplete"); - } - - m_output_width = ((data[4] << 24) | - (data[5] << 16) | - (data[6] << 8) | - (data[7])); - - m_output_height = ((data[8] << 24) | - (data[9] << 16) | - (data[10] << 8) | - (data[11])); - } - else { - m_output_width = ((data[4] << 8) | - (data[5])); - - m_output_height = ((data[6] << 8) | - (data[7])); - } - - return Error::Ok; -} - - -std::vector ImageGrid::write() const -{ - int field_size; - - if (m_output_width > 0xFFFF || - m_output_height > 0xFFFF) { - field_size = 32; - } - else { - field_size = 16; - } - - std::vector data(field_size == 16 ? 8 : 12); - - data[0] = 0; // version - - uint8_t flags = 0; - if (field_size == 32) { - flags |= 1; - } - - data[1] = flags; - data[2] = (uint8_t) (m_rows - 1); - data[3] = (uint8_t) (m_columns - 1); - - if (field_size == 32) { - data[4] = (uint8_t) ((m_output_width >> 24) & 0xFF); - data[5] = (uint8_t) ((m_output_width >> 16) & 0xFF); - data[6] = (uint8_t) ((m_output_width >> 8) & 0xFF); - data[7] = (uint8_t) ((m_output_width) & 0xFF); - - data[8] = (uint8_t) ((m_output_height >> 24) & 0xFF); - data[9] = (uint8_t) ((m_output_height >> 16) & 0xFF); - data[10] = (uint8_t) ((m_output_height >> 8) & 0xFF); - data[11] = (uint8_t) ((m_output_height) & 0xFF); - } - else { - data[4] = (uint8_t) ((m_output_width >> 8) & 0xFF); - data[5] = (uint8_t) ((m_output_width) & 0xFF); - - data[6] = (uint8_t) ((m_output_height >> 8) & 0xFF); - data[7] = (uint8_t) ((m_output_height) & 0xFF); - } - - return data; -} - - -std::string ImageGrid::dump() const -{ - std::ostringstream sstr; - - sstr << "rows: " << m_rows << "\n" - << "columns: " << m_columns << "\n" - << "output width: " << m_output_width << "\n" - << "output height: " << m_output_height << "\n"; - - return sstr.str(); -} - - -class ImageOverlay -{ -public: - Error parse(size_t num_images, const std::vector& data); - - std::string dump() const; - - void get_background_color(uint16_t col[4]) const; - - uint32_t get_canvas_width() const { return m_width; } - - uint32_t get_canvas_height() const { return m_height; } - - size_t get_num_offsets() const { return m_offsets.size(); } - - void get_offset(size_t image_index, int32_t* x, int32_t* y) const; - -private: - uint8_t m_version; - uint8_t m_flags; - uint16_t m_background_color[4]; - uint32_t m_width; - uint32_t m_height; - - struct Offset - { - int32_t x, y; - }; - - std::vector m_offsets; -}; - - -Error ImageOverlay::parse(size_t num_images, const std::vector& data) +HeifContext::HeifContext() { - Error eofError(heif_error_Invalid_input, - heif_suberror_Invalid_grid_data, - "Overlay image data incomplete"); - - if (data.size() < 2 + 4 * 2) { - return eofError; - } - - m_version = data[0]; - m_flags = data[1]; - - if (m_version != 0) { - std::stringstream sstr; - sstr << "Overlay image data version " << ((int) m_version) << " is not implemented yet"; - - return Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_data_version, - sstr.str()); - } - - int field_len = ((m_flags & 1) ? 4 : 2); - int ptr = 2; - - if (ptr + 4 * 2 + 2 * field_len + num_images * 2 * field_len > data.size()) { - return eofError; - } - - for (int i = 0; i < 4; i++) { - uint16_t color = static_cast(readvec(data, ptr, 2)); - m_background_color[i] = color; - } - - m_width = readvec(data, ptr, field_len); - m_height = readvec(data, ptr, field_len); + m_limits = global_security_limits; - m_offsets.resize(num_images); - - for (size_t i = 0; i < num_images; i++) { - m_offsets[i].x = readvec_signed(data, ptr, field_len); - m_offsets[i].y = readvec_signed(data, ptr, field_len); - } - - return Error::Ok; + reset_to_empty_heif(); } -std::string ImageOverlay::dump() const +HeifContext::~HeifContext() { - std::stringstream sstr; - - sstr << "version: " << ((int) m_version) << "\n" - << "flags: " << ((int) m_flags) << "\n" - << "background color: " << m_background_color[0] - << ";" << m_background_color[1] - << ";" << m_background_color[2] - << ";" << m_background_color[3] << "\n" - << "canvas size: " << m_width << "x" << m_height << "\n" - << "offsets: "; - - for (const Offset& offset : m_offsets) { - sstr << offset.x << ";" << offset.y << " "; + // Break circular references between Images (when a faulty input image has circular image references) + for (auto& it : m_all_images) { + std::shared_ptr image = it.second; + image->clear(); } - sstr << "\n"; - - return sstr.str(); } -void ImageOverlay::get_background_color(uint16_t col[4]) const +static void copy_security_limits(heif_security_limits* dst, const heif_security_limits* src) { - for (int i = 0; i < 4; i++) { - col[i] = m_background_color[i]; - } -} + dst->version = 1; + dst->max_image_size_pixels = src->max_image_size_pixels; + dst->max_number_of_tiles = src->max_number_of_tiles; + dst->max_bayer_pattern_pixels = src->max_bayer_pattern_pixels; + dst->max_items = src->max_items; + dst->max_color_profile_size = src->max_color_profile_size; + dst->max_memory_block_size = src->max_memory_block_size; -void ImageOverlay::get_offset(size_t image_index, int32_t* x, int32_t* y) const -{ - assert(image_index < m_offsets.size()); - assert(x && y); - - *x = m_offsets[image_index].x; - *y = m_offsets[image_index].y; + dst->max_iloc_items = src->max_iloc_items; + dst->max_iloc_extents_per_item = src->max_iloc_extents_per_item; + dst->max_children_per_box = src->max_children_per_box; } -HeifContext::HeifContext() +void HeifContext::set_security_limits(const heif_security_limits* limits) { - m_maximum_image_width_limit = MAX_IMAGE_WIDTH; - m_maximum_image_height_limit = MAX_IMAGE_HEIGHT; - - reset_to_empty_heif(); + copy_security_limits(&m_limits, limits); } -HeifContext::~HeifContext() -{ - // Break circular references between Images (when a faulty input image has circular image references) - for (auto& it : m_all_images) { - std::shared_ptr image = it.second; - image->clear(); - } -} Error HeifContext::read(const std::shared_ptr& reader) { m_heif_file = std::make_shared(); + m_heif_file->set_security_limits(&m_limits); Error err = m_heif_file->read(reader); if (err) { return err; @@ -428,6 +149,7 @@ Error HeifContext::read(const std::shared_ptr& reader) Error HeifContext::read_from_file(const char* input_filename) { m_heif_file = std::make_shared(); + m_heif_file->set_security_limits(&m_limits); Error err = m_heif_file->read_from_file(input_filename); if (err) { return err; @@ -439,6 +161,7 @@ Error HeifContext::read_from_file(const char* input_filename) Error HeifContext::read_from_memory(const void* data, size_t size, bool copy) { m_heif_file = std::make_shared(); + m_heif_file->set_security_limits(&m_limits); Error err = m_heif_file->read_from_memory(data, size, copy); if (err) { return err; @@ -450,6 +173,7 @@ Error HeifContext::read_from_memory(const void* data, size_t size, bool copy) void HeifContext::reset_to_empty_heif() { m_heif_file = std::make_shared(); + m_heif_file->set_security_limits(&m_limits); m_heif_file->new_empty_file(); m_all_images.clear(); @@ -457,9 +181,60 @@ void HeifContext::reset_to_empty_heif() m_primary_image.reset(); } + +std::vector> HeifContext::get_top_level_images(bool return_error_images) +{ + if (return_error_images) { + return m_top_level_images; + } + else { + std::vector> filtered; + for (auto& item : m_top_level_images) { + if (!item->get_item_error()) { + filtered.push_back(item); + } + } + + return filtered; + } +} + + +std::shared_ptr HeifContext::get_image(heif_item_id id, bool return_error_images) +{ + auto iter = m_all_images.find(id); + if (iter == m_all_images.end()) { + return nullptr; + } + else { + if (iter->second->get_item_error() && !return_error_images) { + return nullptr; + } + else { + return iter->second; + } + } +} + + +std::shared_ptr HeifContext::get_primary_image(bool return_error_image) +{ + if (!return_error_image && m_primary_image->get_item_error()) + return nullptr; + else + return m_primary_image; +} + + +bool HeifContext::is_image(heif_item_id ID) const +{ + return m_all_images.find(ID) != m_all_images.end(); +} + + std::shared_ptr HeifContext::add_region_item(uint32_t reference_width, uint32_t reference_height) { - std::shared_ptr box = m_heif_file->add_new_infe_box("rgan"); + std::shared_ptr box = m_heif_file->add_new_infe_box(fourcc("rgan")); box->set_hidden_item(true); auto regionItem = std::make_shared(box->get_item_ID(), reference_width, reference_height); @@ -489,9 +264,14 @@ void HeifContext::write(StreamWriter& writer) Error err = region->encode(data_array); // TODO: err - m_heif_file->append_iloc_data(region->item_id, data_array); + m_heif_file->append_iloc_data(region->item_id, data_array, 0); } + // --- post-process images + + for (auto& img : m_all_images) { + img.second->process_before_write(); + } // --- write to file @@ -504,25 +284,27 @@ std::string HeifContext::debug_dump_boxes() const } -static bool item_type_is_image(const std::string& item_type, const std::string& content_type) +static bool item_type_is_image(uint32_t item_type, const std::string& content_type) { - return (item_type == "hvc1" || - item_type == "grid" || - item_type == "iden" || - item_type == "iovl" || - item_type == "av01" || - item_type == "unci" || - item_type == "vvc1" || - item_type == "jpeg" || - (item_type == "mime" && content_type == "image/jpeg") || - item_type == "j2k1" || - item_type == "mski"); + return (item_type == fourcc("hvc1") || + item_type == fourcc("av01") || + item_type == fourcc("grid") || + item_type == fourcc("tili") || + item_type == fourcc("iden") || + item_type == fourcc("iovl") || + item_type == fourcc("avc1") || + item_type == fourcc("unci") || + item_type == fourcc("vvc1") || + item_type == fourcc("jpeg") || + (item_type == fourcc("mime") && content_type == "image/jpeg") || + item_type == fourcc("j2k1") || + item_type == fourcc("mski")); } -void HeifContext::remove_top_level_image(const std::shared_ptr& image) +void HeifContext::remove_top_level_image(const std::shared_ptr& image) { - std::vector> new_list; + std::vector> new_list; for (const auto& img : m_top_level_images) { if (img != image) { @@ -552,33 +334,45 @@ Error HeifContext::interpret_heif_file() continue; } - if (item_type_is_image(infe_box->get_item_type(), infe_box->get_content_type())) { - auto image = std::make_shared(this, id); - m_all_images.insert(std::make_pair(id, image)); + auto image = ImageItem::alloc_for_infe_box(this, infe_box); + if (!image) { + // It is no image item, skip it. + continue; + } - if (!infe_box->is_hidden_item()) { - if (id == m_heif_file->get_primary_image_ID()) { - image->set_primary(true); - m_primary_image = image; - } + m_all_images.insert(std::make_pair(id, image)); - m_top_level_images.push_back(image); + if (!infe_box->is_hidden_item()) { + if (id == m_heif_file->get_primary_image_ID()) { + image->set_primary(true); + m_primary_image = image; } + + m_top_level_images.push_back(image); + } + + Error err = image->on_load_file(); + if (err) { + return err; } } if (!m_primary_image) { return Error(heif_error_Invalid_input, heif_suberror_Nonexisting_item_referenced, - "'pitm' box references a non-existing image"); + "'pitm' box references an unsupported or non-existing image"); } - // --- read through properties for each image and extract image resolutions + // --- process image properties for (auto& pair : m_all_images) { auto& image = pair.second; + if (image->get_item_error()) { + continue; + } + std::vector> properties; Error err = m_heif_file->get_properties(pair.first, properties); @@ -586,6 +380,41 @@ Error HeifContext::interpret_heif_file() return err; } + + // --- are there any 'essential' properties that we did not parse? + + for (const auto& prop : properties) { + if (std::dynamic_pointer_cast(prop) && + get_heif_file()->get_ipco_box()->is_property_essential_for_item(pair.first, prop, get_heif_file()->get_ipma_box())) { + + std::stringstream sstr; + sstr << "could not parse item property '" << prop->get_type_string() << "'"; + return {heif_error_Unsupported_feature, heif_suberror_Unsupported_essential_property, sstr.str()}; + } + } + + + // --- Are there any parse errors in optional properties? Attach the errors as warnings to the images. + + bool ignore_nonfatal_parse_errors = false; // TODO: this should be a user option. Where should we put this (heif_decoding_options, or while creating the context) ? + + for (const auto& prop : properties) { + if (auto errorbox = std::dynamic_pointer_cast(prop)) { + parse_error_fatality fatality = errorbox->get_parse_error_fatality(); + + if (fatality == parse_error_fatality::optional || + (fatality == parse_error_fatality::ignorable && ignore_nonfatal_parse_errors)) { + image->add_decoding_warning(errorbox->get_error()); + } + else { + return errorbox->get_error(); + } + } + } + + + // --- extract image resolution + bool ispe_read = false; for (const auto& prop : properties) { auto ispe = std::dynamic_pointer_cast(prop); @@ -593,18 +422,15 @@ Error HeifContext::interpret_heif_file() uint32_t width = ispe->get_width(); uint32_t height = ispe->get_height(); - - // --- check whether the image size is "too large" - - if (width > m_maximum_image_width_limit || - height > m_maximum_image_height_limit) { + uint32_t max_width_height = static_cast(std::numeric_limits::max()); + if (width >= max_width_height || height >= max_width_height) { std::stringstream sstr; sstr << "Image size " << width << "x" << height << " exceeds the maximum image size " - << m_maximum_image_width_limit << "x" << m_maximum_image_height_limit << "\n"; + << get_security_limits()->max_image_size_pixels << "\n"; return Error(heif_error_Memory_allocation_error, - heif_suberror_Security_limit_exceeded, - sstr.str()); + heif_suberror_Security_limit_exceeded, + sstr.str()); } image->set_resolution(width, height); @@ -656,8 +482,8 @@ Error HeifContext::interpret_heif_file() auto irot = std::dynamic_pointer_cast(prop); if (irot) { - if (irot->get_rotation() == 90 || - irot->get_rotation() == 270) { + if (irot->get_rotation_ccw() == 90 || + irot->get_rotation_ccw() == 270) { // swap width and height image->set_resolution(image->get_height(), image->get_width()); @@ -854,8 +680,12 @@ Error HeifContext::interpret_heif_file() for (auto& pair : m_all_images) { auto& image = pair.second; + if (image->get_item_error()) { + continue; + } + std::shared_ptr infe = m_heif_file->get_infe_box(image->get_id()); - if (infe->get_item_type() == "hvc1") { + if (infe->get_item_type_4cc() == fourcc("hvc1")) { auto ipma = m_heif_file->get_ipma_box(); auto ipco = m_heif_file->get_ipco_box(); @@ -866,7 +696,7 @@ Error HeifContext::interpret_heif_file() "No hvcC property in hvc1 type image"); } } - if (infe->get_item_type() == "vvc1") { + if (infe->get_item_type_4cc() == fourcc("vvc1")) { auto ipma = m_heif_file->get_ipma_box(); auto ipco = m_heif_file->get_ipco_box(); @@ -886,6 +716,10 @@ Error HeifContext::interpret_heif_file() auto& image = pair.second; auto id = pair.first; + if (image->get_item_error()) { + continue; + } + auto infe_box = m_heif_file->get_infe_box(id); if (!infe_box) { continue; @@ -895,7 +729,7 @@ Error HeifContext::interpret_heif_file() break; } - if (infe_box->get_item_type() == "grid") { + if (infe_box->get_item_type_4cc() == fourcc("grid")) { std::vector image_references = iref_box->get_references(id, fourcc("dimg")); if (image_references.empty()) { @@ -924,13 +758,14 @@ Error HeifContext::interpret_heif_file() // --- read metadata and assign to image for (heif_item_id id : image_IDs) { - std::string item_type = m_heif_file->get_item_type(id); + uint32_t item_type = m_heif_file->get_item_type_4cc(id); + std::string content_type = m_heif_file->get_content_type(id); + // 'rgan': skip region annotations, handled next // 'iden': iden images are no metadata - if (item_type == "rgan" || item_type == "iden") { + if (item_type_is_image(item_type, content_type) || item_type == fourcc("rgan")) { continue; } - std::string content_type = m_heif_file->get_content_type(id); std::string item_uri_type = m_heif_file->get_item_uri_type(id); @@ -938,13 +773,13 @@ Error HeifContext::interpret_heif_file() std::shared_ptr metadata = std::make_shared(); metadata->item_id = id; - metadata->item_type = item_type; + metadata->item_type = fourcc_to_string(item_type); metadata->content_type = content_type; metadata->item_uri_type = item_uri_type; - Error err = m_heif_file->get_compressed_image_data(id, &(metadata->m_data)); + Error err = m_heif_file->get_uncompressed_item_data(id, &(metadata->m_data)); if (err) { - if (item_type == "Exif" || item_type == "mime") { + if (item_type == fourcc("Exif") || item_type == fourcc("mime")) { // these item types should have data return err; } @@ -996,69 +831,75 @@ Error HeifContext::interpret_heif_file() // --- read region item and assign to image(s) for (heif_item_id id : image_IDs) { - std::string item_type = m_heif_file->get_item_type(id); - if (item_type == "rgan") { - std::shared_ptr region_item = std::make_shared(); - region_item->item_id = id; - std::vector region_data; - Error err = m_heif_file->get_compressed_image_data(id, &(region_data)); - if (err) { - return err; - } - region_item->parse(region_data); - if (iref_box) { - std::vector references = iref_box->get_references_from(id); - for (const auto& ref : references) { - if (ref.header.get_short_type() == fourcc("cdsc")) { - std::vector refs = ref.to_item_ID; - for (uint32_t ref: refs) { - uint32_t image_id = ref; - auto img_iter = m_all_images.find(image_id); - if (img_iter == m_all_images.end()) { - return Error(heif_error_Invalid_input, - heif_suberror_Nonexisting_item_referenced, - "Region item assigned to non-existing image"); - } - img_iter->second->add_region_item_id(id); - m_region_items.push_back(region_item); + uint32_t item_type = m_heif_file->get_item_type_4cc(id); + if (item_type != fourcc("rgan")) { + continue; + } + + std::shared_ptr region_item = std::make_shared(); + region_item->item_id = id; + std::vector region_data; + Error err = m_heif_file->get_uncompressed_item_data(id, ®ion_data); + if (err) { + return err; + } + region_item->parse(region_data); + if (iref_box) { + std::vector references = iref_box->get_references_from(id); + for (const auto& ref : references) { + if (ref.header.get_short_type() == fourcc("cdsc")) { + std::vector refs = ref.to_item_ID; + for (uint32_t ref : refs) { + uint32_t image_id = ref; + auto img_iter = m_all_images.find(image_id); + if (img_iter == m_all_images.end()) { + return Error(heif_error_Invalid_input, + heif_suberror_Nonexisting_item_referenced, + "Region item assigned to non-existing image"); } + img_iter->second->add_region_item_id(id); + m_region_items.push_back(region_item); } + } - /* When the geometry 'mask' of a region is represented by a mask stored in - * another image item the image item containing the mask shall be identified - * by an item reference of type 'mask' from the region item to the image item - * containing the mask. */ - if (ref.header.get_short_type() == fourcc("mask")) { - std::vector refs = ref.to_item_ID; - size_t mask_index = 0; - for (int j = 0; j < region_item->get_number_of_regions(); j++) { - if (region_item->get_regions()[j]->getRegionType() == heif_region_type_referenced_mask) { - std::shared_ptr mask_geometry = std::dynamic_pointer_cast(region_item->get_regions()[j]); - - if (mask_index >= refs.size()) { - return Error(heif_error_Invalid_input, - heif_suberror_Unspecified, - "Region mask reference with non-existing mask image reference"); - } + /* When the geometry 'mask' of a region is represented by a mask stored in + * another image item the image item containing the mask shall be identified + * by an item reference of type 'mask' from the region item to the image item + * containing the mask. */ + if (ref.header.get_short_type() == fourcc("mask")) { + std::vector refs = ref.to_item_ID; + size_t mask_index = 0; + for (int j = 0; j < region_item->get_number_of_regions(); j++) { + if (region_item->get_regions()[j]->getRegionType() == heif_region_type_referenced_mask) { + std::shared_ptr mask_geometry = std::dynamic_pointer_cast(region_item->get_regions()[j]); - uint32_t mask_image_id = refs[mask_index]; - if (!is_image(mask_image_id)) { - return Error(heif_error_Invalid_input, - heif_suberror_Unspecified, - "Region mask referenced item is not an image"); - } + if (mask_index >= refs.size()) { + return Error(heif_error_Invalid_input, + heif_suberror_Unspecified, + "Region mask reference with non-existing mask image reference"); + } - auto mask_image = m_all_images.find(mask_image_id)->second; - mask_geometry->referenced_item = mask_image_id; - if (mask_geometry->width == 0) { - mask_geometry->width = mask_image->get_ispe_width(); - } - if (mask_geometry->height == 0) { - mask_geometry->height = mask_image->get_ispe_height(); - } - mask_index += 1; - remove_top_level_image(mask_image); + uint32_t mask_image_id = refs[mask_index]; + if (!is_image(mask_image_id)) { + return Error(heif_error_Invalid_input, + heif_suberror_Unspecified, + "Region mask referenced item is not an image"); + } + + auto mask_image = get_image(mask_image_id, true); + if (auto error = mask_image->get_item_error()) { + return error; + } + + mask_geometry->referenced_item = mask_image_id; + if (mask_geometry->width == 0) { + mask_geometry->width = mask_image->get_ispe_width(); + } + if (mask_geometry->height == 0) { + mask_geometry->height = mask_image->get_ispe_height(); } + mask_index += 1; + remove_top_level_image(mask_image); } } } @@ -1070,2526 +911,312 @@ Error HeifContext::interpret_heif_file() } -HeifContext::Image::Image(HeifContext* context, heif_item_id id) - : m_heif_context(context), - m_id(id) +bool HeifContext::has_alpha(heif_item_id ID) const { - memset(&m_depth_representation_info, 0, sizeof(m_depth_representation_info)); -} + assert(is_image(ID)); + auto img = m_all_images.find(ID)->second; -HeifContext::Image::~Image() = default; + // --- has the image an auxiliary alpha image? -bool HeifContext::is_image(heif_item_id ID) const -{ - for (const auto& img : m_all_images) { - if (img.first == ID) - return true; + if (img->get_alpha_channel() != nullptr) { + return true; } - return false; -} - - -bool HeifContext::has_alpha(heif_item_id ID) const -{ - - assert(is_image(ID)); - auto img = m_all_images.find(ID)->second; - - // --- has the image an auxiliary alpha image? + heif_colorspace colorspace; + heif_chroma chroma; + Error err = img->get_coded_image_colorspace(&colorspace, &chroma); + if (err) { + return false; + } - if (img->get_alpha_channel() != nullptr) { + if (chroma == heif_chroma_interleaved_RGBA || + chroma == heif_chroma_interleaved_RRGGBBAA_BE || + chroma == heif_chroma_interleaved_RRGGBBAA_LE) { return true; } // --- if the image is a 'grid', check if there is alpha in any of the tiles - std::string image_type = m_heif_file->get_item_type(ID); - if (image_type == "grid") { + // TODO: move this into ImageItem + + uint32_t image_type = m_heif_file->get_item_type_4cc(ID); + if (image_type == fourcc("grid")) { std::vector grid_data; - Error error = m_heif_file->get_compressed_image_data(ID, &grid_data); + Error error = m_heif_file->get_uncompressed_item_data(ID, &grid_data); if (error) { return false; } - ImageGrid grid; - Error err = grid.parse(grid_data); - if (err) { - return false; - } - - - auto iref_box = m_heif_file->get_iref_box(); - - if (!iref_box) { - return false; - } - - std::vector image_references = iref_box->get_references(ID, fourcc("dimg")); - - if ((int) image_references.size() != grid.get_rows() * grid.get_columns()) { - return false; - } - - - // --- check that all image IDs are valid images - - for (heif_item_id tile_id : image_references) { - if (!is_image(tile_id)) { - return false; - } - } - - // --- check whether at least one tile has an alpha channel - - bool has_alpha = false; - - for (heif_item_id tile_id : image_references) { - auto iter = m_all_images.find(tile_id); - if (iter == m_all_images.end()) { - return false; - } - - const std::shared_ptr tileImg = iter->second; - - has_alpha |= tileImg->get_alpha_channel() != nullptr; - } - - return has_alpha; - } - else { - // TODO: what about overlays ? - return false; - } -} - - -Error HeifContext::get_id_of_non_virtual_child_image(heif_item_id id, heif_item_id& out) const -{ - std::string image_type = m_heif_file->get_item_type(id); - if (image_type == "grid" || - image_type == "iden" || - image_type == "iovl") { - auto iref_box = m_heif_file->get_iref_box(); - if (!iref_box) { - return Error(heif_error_Invalid_input, - heif_suberror_No_item_data, - "Derived image does not reference any other image items"); - } - - std::vector image_references = iref_box->get_references(id, fourcc("dimg")); - - // TODO: check whether this really can be recursive (e.g. overlay of grid images) - - if (image_references.empty() || image_references[0] == id) { - return Error(heif_error_Invalid_input, - heif_suberror_No_item_data, - "Derived image does not reference any other image items"); - } - else { - return get_id_of_non_virtual_child_image(image_references[0], out); - } - } - else { - out = id; - return Error::Ok; - } -} - - -int HeifContext::Image::get_ispe_width() const -{ - auto ispe = m_heif_context->m_heif_file->get_property(m_id); - if (!ispe) { - return 0; - } - else { - return ispe->get_width(); - } -} - - -int HeifContext::Image::get_ispe_height() const -{ - auto ispe = m_heif_context->m_heif_file->get_property(m_id); - if (!ispe) { - return 0; - } - else { - return ispe->get_height(); - } -} - - -Error HeifContext::Image::get_preferred_decoding_colorspace(heif_colorspace* out_colorspace, heif_chroma* out_chroma) const -{ - heif_item_id id; - Error err = m_heif_context->get_id_of_non_virtual_child_image(m_id, id); - if (err) { - return err; - } - - auto pixi = m_heif_context->m_heif_file->get_property(id); - if (pixi && pixi->get_num_channels() == 1) { - *out_colorspace = heif_colorspace_monochrome; - *out_chroma = heif_chroma_monochrome; - return err; - } - - auto nclx = get_color_profile_nclx(); - if (nclx && nclx->get_matrix_coefficients() == 0) { - *out_colorspace = heif_colorspace_RGB; - *out_chroma = heif_chroma_444; - return err; - } - - // TODO: this should be codec specific. JPEG 2000, for example, can use RGB internally. - - *out_colorspace = heif_colorspace_YCbCr; - *out_chroma = heif_chroma_undefined; - - if (auto hvcC = m_heif_context->m_heif_file->get_property(id)) { - *out_chroma = (heif_chroma)(hvcC->get_configuration().chroma_format); - } - else if (auto vvcC = m_heif_context->m_heif_file->get_property(id)) { - *out_chroma = (heif_chroma)(vvcC->get_configuration().chroma_format_idc); - } - else if (auto av1C = m_heif_context->m_heif_file->get_property(id)) { - *out_chroma = (heif_chroma)(av1C->get_configuration().get_heif_chroma()); - } - else if (auto j2kH = m_heif_context->m_heif_file->get_property(id)) { - JPEG2000MainHeader jpeg2000Header; - err = jpeg2000Header.parseHeader(*m_heif_context->m_heif_file, id); - if (err) { - return err; - } - *out_chroma = jpeg2000Header.get_chroma_format(); - } - - return err; -} - - -int HeifContext::Image::get_luma_bits_per_pixel() const -{ - heif_item_id id; - Error err = m_heif_context->get_id_of_non_virtual_child_image(m_id, id); - if (err) { - return -1; - } - - // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage) - return m_heif_context->m_heif_file->get_luma_bits_per_pixel_from_configuration(id); -} - - -int HeifContext::Image::get_chroma_bits_per_pixel() const -{ - heif_item_id id; - Error err = m_heif_context->get_id_of_non_virtual_child_image(m_id, id); - if (err) { - return -1; - } - - // NOLINTNEXTLINE(clang-analyzer-core.CallAndMessage) - return m_heif_context->m_heif_file->get_chroma_bits_per_pixel_from_configuration(id); -} - - -Error HeifContext::decode_image_user(heif_item_id ID, - std::shared_ptr& img, - heif_colorspace out_colorspace, - heif_chroma out_chroma, - const struct heif_decoding_options& options) const -{ - Error err = decode_image_planar(ID, img, out_colorspace, options, false); - if (err) { - return err; - } - - // --- convert to output chroma format - - heif_colorspace target_colorspace = (out_colorspace == heif_colorspace_undefined ? - img->get_colorspace() : - out_colorspace); - - heif_chroma target_chroma = (out_chroma == heif_chroma_undefined ? - img->get_chroma_format() : out_chroma); - - bool different_chroma = (target_chroma != img->get_chroma_format()); - bool different_colorspace = (target_colorspace != img->get_colorspace()); - - int bpp = options.convert_hdr_to_8bit ? 8 : 0; - // TODO: check BPP changed - if (different_chroma || different_colorspace) { - - img = convert_colorspace(img, target_colorspace, target_chroma, nullptr, bpp, options.color_conversion_options); - if (!img) { - return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_color_conversion); - } - } - - return Error::Ok; -} - - -Error HeifContext::decode_image_planar(heif_item_id ID, - std::shared_ptr& img, - heif_colorspace out_colorspace, - const struct heif_decoding_options& options, bool alphaImage) const -{ - std::string image_type = m_heif_file->get_item_type(ID); - - std::shared_ptr imginfo; - if (m_all_images.find(ID) != m_all_images.end()) { - imginfo = m_all_images.find(ID)->second; - } - - // Note: this may happen, for example when an 'iden' image references a non-existing image item. - if (imginfo == nullptr) { - return Error(heif_error_Invalid_input, heif_suberror_Nonexisting_item_referenced); - } - - Error error; - - - // --- decode image, depending on its type - - if (image_type == "hvc1" || - image_type == "vvc1" || - image_type == "av01" || - image_type == "j2k1" || - image_type == "jpeg" || - (image_type == "mime" && m_heif_file->get_content_type(ID) == "image/jpeg")) { - - heif_compression_format compression = heif_compression_undefined; - if (image_type == "hvc1") { - compression = heif_compression_HEVC; - } - else if (image_type == "vvc1") { - compression = heif_compression_VVC; - } - else if (image_type == "av01") { - compression = heif_compression_AV1; - } - else if (image_type == "jpeg" || - (image_type == "mime" && m_heif_file->get_content_type(ID) == "image/jpeg")) { - compression = heif_compression_JPEG; - } - else if (image_type == "j2k1") { - compression = heif_compression_JPEG2000; - } - - const struct heif_decoder_plugin* decoder_plugin = get_decoder(compression, options.decoder_id); - if (!decoder_plugin) { - return Error(heif_error_Plugin_loading_error, heif_suberror_No_matching_decoder_installed); - } - - std::vector data; - error = m_heif_file->get_compressed_image_data(ID, &data); - if (error) { - return error; - } - - void* decoder; - struct heif_error err = decoder_plugin->new_decoder(&decoder); - if (err.code != heif_error_Ok) { - return Error(err.code, err.subcode, err.message); - } - - if (decoder_plugin->plugin_api_version >= 2) { - if (decoder_plugin->set_strict_decoding) { - decoder_plugin->set_strict_decoding(decoder, options.strict_decoding); - } - } - - err = decoder_plugin->push_data(decoder, data.data(), data.size()); - if (err.code != heif_error_Ok) { - decoder_plugin->free_decoder(decoder); - return Error(err.code, err.subcode, err.message); - } - - //std::shared_ptr* decoded_img; - - heif_image* decoded_img = nullptr; - - err = decoder_plugin->decode_image(decoder, &decoded_img); - if (err.code != heif_error_Ok) { - decoder_plugin->free_decoder(decoder); - return Error(err.code, err.subcode, err.message); - } - - if (!decoded_img) { - // TODO(farindk): The plugin should return an error in this case. - decoder_plugin->free_decoder(decoder); - return Error(heif_error_Decoder_plugin_error, heif_suberror_Unspecified); - } - - img = std::move(decoded_img->image); - heif_image_release(decoded_img); - - decoder_plugin->free_decoder(decoder); - - - - // --- convert to output chroma format - - // If there is an NCLX profile in the HEIF/AVIF metadata, use this for the color conversion. - // Otherwise, use the profile that is stored in the image stream itself and then set the - // (non-NCLX) profile later. - auto nclx = imginfo->get_color_profile_nclx(); - if (nclx) { - img->set_color_profile_nclx(nclx); - } - - auto icc = imginfo->get_color_profile_icc(); - if (icc) { - img->set_color_profile_icc(icc); - } - - if (alphaImage) { - // no color conversion required - } - else { - heif_colorspace target_colorspace = (out_colorspace == heif_colorspace_undefined ? - img->get_colorspace() : - out_colorspace); - - if (!alphaImage && target_colorspace == heif_colorspace_YCbCr) { - target_colorspace = heif_colorspace_RGB; - } - - heif_chroma target_chroma = (target_colorspace == heif_colorspace_monochrome ? - heif_chroma_monochrome : heif_chroma_444); - - bool different_chroma = (target_chroma != img->get_chroma_format()); - bool different_colorspace = (target_colorspace != img->get_colorspace()); - - if (different_chroma || different_colorspace) { - img = convert_colorspace(img, target_colorspace, target_chroma, nullptr, 0, options.color_conversion_options); - if (!img) { - return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_color_conversion); - } - } - } - } - else if (image_type == "grid") { - std::vector data; - error = m_heif_file->get_compressed_image_data(ID, &data); - if (error) { - return error; - } - - error = decode_full_grid_image(ID, img, data, options); - if (error) { - return error; - } - } - else if (image_type == "iden") { - error = decode_derived_image(ID, img, options); - if (error) { - return error; - } - } - else if (image_type == "iovl") { - std::vector data; - error = m_heif_file->get_compressed_image_data(ID, &data); - if (error) { - return error; - } - - error = decode_overlay_image(ID, img, data, options); - if (error) { - return error; - } -#if WITH_UNCOMPRESSED_CODEC - } - else if (image_type == "unci") { - std::vector data; - error = m_heif_file->get_compressed_image_data(ID, &data); - if (error) { - return error; - } - error = UncompressedImageCodec::decode_uncompressed_image(m_heif_file, - ID, - img, - m_maximum_image_width_limit, - m_maximum_image_height_limit, - data); - if (error) { - return error; - } -#endif - } - else if (image_type == "mski") { - std::vector data; - error = m_heif_file->get_compressed_image_data(ID, &data); - if (error) { - std::cout << "mski error 1" << std::endl; - return error; - } - error = MaskImageCodec::decode_mask_image(m_heif_file, - ID, - img, - m_maximum_image_width_limit, - m_maximum_image_height_limit, - data); - if (error) { - return error; - } - } - else { - // Should not reach this, was already rejected by "get_image_data". - return Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_image_type); - } - - - - // --- apply image transformations - - if (options.ignore_transformations == false) { - std::vector> properties; - auto ipco_box = m_heif_file->get_ipco_box(); - auto ipma_box = m_heif_file->get_ipma_box(); - error = ipco_box->get_properties_for_item_ID(ID, ipma_box, properties); - - for (const auto& property : properties) { - if (property->get_short_type() == fourcc("irot")) { - auto rot = std::dynamic_pointer_cast(property); - std::shared_ptr rotated_img; - error = img->rotate_ccw(rot->get_rotation(), rotated_img); - if (error) { - return error; - } - - img = rotated_img; - } - - - if (property->get_short_type() == fourcc("imir")) { - auto mirror = std::dynamic_pointer_cast(property); - error = img->mirror_inplace(mirror->get_mirror_direction()); - if (error) { - return error; - } - } - - - if (property->get_short_type() == fourcc("clap")) { - auto clap = std::dynamic_pointer_cast(property); - std::shared_ptr clap_img; - - int img_width = img->get_width(); - int img_height = img->get_height(); - assert(img_width >= 0); - assert(img_height >= 0); - - int left = clap->left_rounded(img_width); - int right = clap->right_rounded(img_width); - int top = clap->top_rounded(img_height); - int bottom = clap->bottom_rounded(img_height); - - if (left < 0) { left = 0; } - if (top < 0) { top = 0; } - - if (right >= img_width) { right = img_width - 1; } - if (bottom >= img_height) { bottom = img_height - 1; } - - if (left > right || - top > bottom) { - return Error(heif_error_Invalid_input, - heif_suberror_Invalid_clean_aperture); - } - - std::shared_ptr cropped_img; - error = img->crop(left, right, top, bottom, cropped_img); - if (error) { - return error; - } - - img = cropped_img; - } - } - } - - - // --- add alpha channel, if available - - // TODO: this if statement is probably wrong. When we have a tiled image with alpha - // channel, then the alpha images should be associated with their respective tiles. - // However, the tile images are not part of the m_all_images list. - // Fix this, when we have a test image available. - if (m_all_images.find(ID) != m_all_images.end()) { - const auto imginfo = m_all_images.find(ID)->second; - - std::shared_ptr alpha_image = imginfo->get_alpha_channel(); - if (alpha_image) { - std::shared_ptr alpha; - Error err = decode_image_planar(alpha_image->get_id(), alpha, - heif_colorspace_undefined, options, true); - if (err) { - return err; - } - - // TODO: check that sizes are the same and that we have an Y channel - // BUT: is there any indication in the standard that the alpha channel should have the same size? - - heif_channel channel; - switch (alpha->get_colorspace()) { - case heif_colorspace_YCbCr: - case heif_colorspace_monochrome: - channel = heif_channel_Y; - break; - case heif_colorspace_RGB: - channel = heif_channel_R; - break; - case heif_colorspace_undefined: - default: - return Error(heif_error_Invalid_input, - heif_suberror_Unsupported_color_conversion); - } - - - // TODO: we should include a decoding option to control whether libheif should automatically scale the alpha channel, and if so, which scaling filter (enum: Off, NN, Bilinear, ...). - // It might also be that a specific output format implies that alpha is scaled (RGBA32). That would favor an enum for the scaling filter option + a bool to switch auto-filtering on. - // But we can only do this when libheif itself doesn't assume anymore that the alpha channel has the same resolution. - - if ((alpha_image->get_width() != img->get_width()) || (alpha_image->get_height() != img->get_height())) { - std::shared_ptr scaled_alpha; - err = alpha->scale_nearest_neighbor(scaled_alpha, img->get_width(), img->get_height()); - if (err) { - return err; - } - alpha = std::move(scaled_alpha); - } - img->transfer_plane_from_image_as(alpha, channel, heif_channel_Alpha); - - if (imginfo->is_premultiplied_alpha()) { - img->set_premultiplied_alpha(true); - } - } - } - - - // --- attach metadata to image - - { - auto ipco_box = m_heif_file->get_ipco_box(); - auto ipma_box = m_heif_file->get_ipma_box(); - - // CLLI - - auto clli_box = ipco_box->get_property_for_item_ID(ID, ipma_box, fourcc("clli")); - auto clli = std::dynamic_pointer_cast(clli_box); - - if (clli) { - img->set_clli(clli->clli); - } - - // MDCV - - auto mdcv_box = ipco_box->get_property_for_item_ID(ID, ipma_box, fourcc("mdcv")); - auto mdcv = std::dynamic_pointer_cast(mdcv_box); - - if (mdcv) { - img->set_mdcv(mdcv->mdcv); - } - - // PASP - - auto pasp_box = ipco_box->get_property_for_item_ID(ID, ipma_box, fourcc("pasp")); - auto pasp = std::dynamic_pointer_cast(pasp_box); - - if (pasp) { - img->set_pixel_ratio(pasp->hSpacing, pasp->vSpacing); - } - } - - return Error::Ok; -} - - -// This function only works with RGB images. -Error HeifContext::decode_full_grid_image(heif_item_id ID, - std::shared_ptr& img, - const std::vector& grid_data, - const heif_decoding_options& options) const -{ - ImageGrid grid; - Error err = grid.parse(grid_data); - if (err) { - return err; - } - - //std::cout << grid.dump(); - - - auto iref_box = m_heif_file->get_iref_box(); - - if (!iref_box) { - return Error(heif_error_Invalid_input, - heif_suberror_No_iref_box, - "No iref box available, but needed for grid image"); - } - - std::vector image_references = iref_box->get_references(ID, fourcc("dimg")); - - if ((int) image_references.size() != grid.get_rows() * grid.get_columns()) { - std::stringstream sstr; - sstr << "Tiled image with " << grid.get_rows() << "x" << grid.get_columns() << "=" - << (grid.get_rows() * grid.get_columns()) << " tiles, but only " - << image_references.size() << " tile images in file"; - - return Error(heif_error_Invalid_input, - heif_suberror_Missing_grid_images, - sstr.str()); - } - - - // --- check that all image IDs are valid images - - for (heif_item_id tile_id : image_references) { - if (!is_image(tile_id)) { - std::stringstream sstr; - sstr << "Tile image ID=" << tile_id << " is not a proper image."; - - return Error(heif_error_Invalid_input, - heif_suberror_Missing_grid_images, - sstr.str()); - } - } - - - auto ipma = m_heif_file->get_ipma_box(); - auto ipco = m_heif_file->get_ipco_box(); - auto pixi_box = ipco->get_property_for_item_ID(ID, ipma, fourcc("pixi")); - auto pixi = std::dynamic_pointer_cast(pixi_box); - - const uint32_t w = grid.get_width(); - const uint32_t h = grid.get_height(); - - - // --- determine output image chroma size and make sure all tiles have same chroma - - assert(!image_references.empty()); - - heif_chroma tile_chroma = heif_chroma_444; - /* TODO: in the future, we might support RGB and mono as intermediate formats - heif_chroma tile_chroma = m_heif_file->get_image_chroma_from_configuration(some_tile_id); - if (tile_chroma != heif_chroma_monochrome) { - tile_chroma = heif_chroma_RGB; - } - */ - - // --- generate image of full output size - - if (w >= m_maximum_image_width_limit || h >= m_maximum_image_height_limit) { - std::stringstream sstr; - sstr << "Image size " << w << "x" << h << " exceeds the maximum image size " - << m_maximum_image_width_limit << "x" << m_maximum_image_height_limit << "\n"; - - return Error(heif_error_Memory_allocation_error, - heif_suberror_Security_limit_exceeded, - sstr.str()); - } - - - img = std::make_shared(); - img->create(w, h, - heif_colorspace_RGB, - heif_chroma_444); - - int bpp = 0; - - if (pixi) { - if (pixi->get_num_channels() < 1) { - return Error(heif_error_Invalid_input, - heif_suberror_Invalid_pixi_box, - "No pixi information for luma channel."); - } - - bpp = pixi->get_bits_per_channel(0); - - if (tile_chroma != heif_chroma_monochrome) { - - // there are broken files that save only a one-channel pixi for an RGB image (issue #283) - if (pixi->get_num_channels() == 3) { - - int bpp_c1 = pixi->get_bits_per_channel(1); - int bpp_c2 = pixi->get_bits_per_channel(2); - - if (bpp_c1 != bpp || bpp_c2 != bpp) { - // TODO: is this really an error? Does the pixi depths refer to RGB or YCbCr? - return Error(heif_error_Invalid_input, - heif_suberror_Invalid_pixi_box, - "Different number of bits per pixel in each channel."); - } - } - } - } - else { - // When there is no pixi-box, get the pixel-depth from one of the tile images - - heif_item_id tileID = image_references[0]; - - auto iter = m_all_images.find(tileID); - if (iter == m_all_images.end()) { - return Error(heif_error_Invalid_input, - heif_suberror_Missing_grid_images, - "Nonexistent grid image referenced"); - } - - const std::shared_ptr tileImg = iter->second; - bpp = tileImg->get_luma_bits_per_pixel(); - } - - if (bpp < 8 || bpp > 16) { - return Error(heif_error_Invalid_input, - heif_suberror_Invalid_pixi_box, - "Invalid bits per pixel in pixi box."); - } - - if (tile_chroma == heif_chroma_monochrome) { - img->add_plane(heif_channel_Y, w, h, bpp); - } - else { - img->add_plane(heif_channel_R, w, h, bpp); - img->add_plane(heif_channel_G, w, h, bpp); - img->add_plane(heif_channel_B, w, h, bpp); - } - - int y0 = 0; - int reference_idx = 0; - -#if ENABLE_PARALLEL_TILE_DECODING - // remember which tile to put where into the image - struct tile_data - { - heif_item_id tileID; - int x_origin, y_origin; - }; - - std::deque tiles; - if (m_max_decoding_threads > 0) - tiles.resize(grid.get_rows() * grid.get_columns()); - - std::deque > errs; -#endif - - for (int y = 0; y < grid.get_rows(); y++) { - int x0 = 0; - int tile_height = 0; - - for (int x = 0; x < grid.get_columns(); x++) { - - heif_item_id tileID = image_references[reference_idx]; - - auto iter = m_all_images.find(tileID); - if (iter == m_all_images.end()) { - return Error(heif_error_Invalid_input, - heif_suberror_Missing_grid_images, - "Nonexistent grid image referenced"); - } - - const std::shared_ptr tileImg = iter->second; - int src_width = tileImg->get_width(); - int src_height = tileImg->get_height(); - -#if ENABLE_PARALLEL_TILE_DECODING - if (m_max_decoding_threads > 0) - tiles[x + y * grid.get_columns()] = tile_data{tileID, x0, y0}; - else -#else - if (1) -#endif - { - Error err = decode_and_paste_tile_image(tileID, img, x0, y0, options); - if (err) { - return err; - } - } - - x0 += src_width; - tile_height = src_height; // TODO: check that all tiles have the same height - - reference_idx++; - } - - y0 += tile_height; - } - -#if ENABLE_PARALLEL_TILE_DECODING - if (m_max_decoding_threads > 0) { - // Process all tiles in a set of background threads. - // Do not start more than the maximum number of threads. - - while (tiles.empty() == false) { - - // If maximum number of threads running, wait until first thread finishes - - if (errs.size() >= (size_t) m_max_decoding_threads) { - Error e = errs.front().get(); - if (e) { - return e; - } - - errs.pop_front(); - } - - - // Start a new decoding thread - - tile_data data = tiles.front(); - tiles.pop_front(); - - errs.push_back(std::async(std::launch::async, - &HeifContext::decode_and_paste_tile_image, this, - data.tileID, img, data.x_origin, data.y_origin, options)); - } - - // check for decoding errors in remaining tiles - - while (errs.empty() == false) { - Error e = errs.front().get(); - if (e) { - return e; - } - - errs.pop_front(); - } - } -#endif - - return Error::Ok; -} - - -Error HeifContext::decode_and_paste_tile_image(heif_item_id tileID, - const std::shared_ptr& img, - int x0, int y0, - const heif_decoding_options& options) const -{ - std::shared_ptr tile_img; - - Error err = decode_image_planar(tileID, tile_img, img->get_colorspace(), options, false); - if (err != Error::Ok) { - return err; - } - - const int w = img->get_width(); - const int h = img->get_height(); - - - // --- copy tile into output image - - int src_width = tile_img->get_width(); - int src_height = tile_img->get_height(); - assert(src_width >= 0); - assert(src_height >= 0); - - heif_chroma chroma = img->get_chroma_format(); - - if (chroma != tile_img->get_chroma_format()) { - return Error(heif_error_Invalid_input, - heif_suberror_Wrong_tile_image_chroma_format, - "Image tile has different chroma format than combined image"); - } - - // --- add alpha plane if we discovered a tile with alpha - - if (tile_img->has_alpha() && !img->has_alpha()) { -#if ENABLE_PARALLEL_TILE_DECODING - // The mutex should probably be a member of heif_context, but since this is so infrequently locked, it probably doesn't matter. - static std::mutex m; - std::lock_guard lock(m); - if (!img->has_channel(heif_channel_Alpha)) // check again, after locking -#endif - { - int alpha_bpp = tile_img->get_bits_per_pixel(heif_channel_Alpha); - - assert(alpha_bpp <= 16); - - uint16_t alpha_default_value = static_cast((1UL << alpha_bpp) - 1UL); - - img->fill_new_plane(heif_channel_Alpha, alpha_default_value, w, h, alpha_bpp); - } - } - - std::set channels = tile_img->get_channel_set(); - - for (heif_channel channel : channels) { - - int tile_stride; - uint8_t* tile_data = tile_img->get_plane(channel, &tile_stride); - - int out_stride; - uint8_t* out_data = img->get_plane(channel, &out_stride); - - if (w <= x0 || h <= y0) { - return Error(heif_error_Invalid_input, - heif_suberror_Invalid_grid_data); - } - - if (img->get_bits_per_pixel(channel) != tile_img->get_bits_per_pixel(channel)) { - return Error(heif_error_Invalid_input, - heif_suberror_Wrong_tile_image_pixel_depth); - } - - int copy_width = std::min(src_width, w - x0); - int copy_height = std::min(src_height, h - y0); - - copy_width *= tile_img->get_storage_bits_per_pixel(heif_channel_R) / 8; - - int xs = x0, ys = y0; - xs *= tile_img->get_storage_bits_per_pixel(heif_channel_R) / 8; - - for (int py = 0; py < copy_height; py++) { - memcpy(out_data + xs + (ys + py) * out_stride, - tile_data + py * tile_stride, - copy_width); - } - } - - return Error::Ok; -} - - -Error HeifContext::decode_derived_image(heif_item_id ID, - std::shared_ptr& img, - const heif_decoding_options& options) const -{ - // find the ID of the image this image is derived from - - auto iref_box = m_heif_file->get_iref_box(); - - if (!iref_box) { - return Error(heif_error_Invalid_input, - heif_suberror_No_iref_box, - "No iref box available, but needed for iden image"); - } - - std::vector image_references = iref_box->get_references(ID, fourcc("dimg")); - - if ((int) image_references.size() != 1) { - return Error(heif_error_Invalid_input, - heif_suberror_Unspecified, - "'iden' image with more than one reference image"); - } - - - heif_item_id reference_image_id = image_references[0]; - - if (reference_image_id == ID) { - return Error(heif_error_Invalid_input, - heif_suberror_Unspecified, - "'iden' image referring to itself"); - } - - Error error = decode_image_planar(reference_image_id, img, - heif_colorspace_RGB, options, false); // TODO: always RGB ? - return error; -} - - -Error HeifContext::decode_overlay_image(heif_item_id ID, - std::shared_ptr& img, - const std::vector& overlay_data, - const heif_decoding_options& options) const -{ - // find the IDs this image is composed of - - auto iref_box = m_heif_file->get_iref_box(); - - if (!iref_box) { - return Error(heif_error_Invalid_input, - heif_suberror_No_iref_box, - "No iref box available, but needed for iovl image"); - } - - std::vector image_references = iref_box->get_references(ID, fourcc("dimg")); - - /* TODO: probably, it is valid that an iovl image has no references ? - - if (image_references.empty()) { - return Error(heif_error_Invalid_input, - heif_suberror_Missing_grid_images, - "'iovl' image with more than one reference image"); - } - */ - - - ImageOverlay overlay; - Error err = overlay.parse(image_references.size(), overlay_data); - if (err) { - return err; - } - - if (image_references.size() != overlay.get_num_offsets()) { - return Error(heif_error_Invalid_input, - heif_suberror_Invalid_overlay_data, - "Number of image offsets does not match the number of image references"); - } - - uint32_t w = overlay.get_canvas_width(); - uint32_t h = overlay.get_canvas_height(); - - if (w >= m_maximum_image_width_limit || h >= m_maximum_image_height_limit) { - std::stringstream sstr; - sstr << "Image size " << w << "x" << h << " exceeds the maximum image size " - << m_maximum_image_width_limit << "x" << m_maximum_image_height_limit << "\n"; - - return Error(heif_error_Memory_allocation_error, - heif_suberror_Security_limit_exceeded, - sstr.str()); - } - - // TODO: seems we always have to compose this in RGB since the background color is an RGB value - img = std::make_shared(); - img->create(w, h, - heif_colorspace_RGB, - heif_chroma_444); - img->add_plane(heif_channel_R, w, h, 8); // TODO: other bit depths - img->add_plane(heif_channel_G, w, h, 8); // TODO: other bit depths - img->add_plane(heif_channel_B, w, h, 8); // TODO: other bit depths - - uint16_t bkg_color[4]; - overlay.get_background_color(bkg_color); - - err = img->fill_RGB_16bit(bkg_color[0], bkg_color[1], bkg_color[2], bkg_color[3]); - if (err) { - return err; - } - - - for (size_t i = 0; i < image_references.size(); i++) { - std::shared_ptr overlay_img; - err = decode_image_planar(image_references[i], overlay_img, - heif_colorspace_RGB, options, false); // TODO: always RGB? Probably yes, because of RGB background color. - if (err != Error::Ok) { - return err; - } - - overlay_img = convert_colorspace(overlay_img, heif_colorspace_RGB, heif_chroma_444, nullptr, 0, options.color_conversion_options); - if (!overlay_img) { - return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_color_conversion); - } - - int32_t dx, dy; - overlay.get_offset(i, &dx, &dy); - - err = img->overlay(overlay_img, dx, dy); - if (err) { - if (err.error_code == heif_error_Invalid_input && - err.sub_error_code == heif_suberror_Overlay_image_outside_of_canvas) { - // NOP, ignore this error - - err = Error::Ok; - } - else { - return err; - } - } - } - - return err; -} - - -static std::shared_ptr -create_alpha_image_from_image_alpha_channel(const std::shared_ptr& image) -{ - // --- generate alpha image - - std::shared_ptr alpha_image = std::make_shared(); - alpha_image->create(image->get_width(), image->get_height(), - heif_colorspace_monochrome, heif_chroma_monochrome); - alpha_image->copy_new_plane_from(image, heif_channel_Alpha, heif_channel_Y); - - - // --- set nclx profile with full-range flag - - auto nclx = std::make_shared(); - nclx->set_undefined(); - nclx->set_full_range_flag(true); // this is the default, but just to be sure in case the defaults change - alpha_image->set_color_profile_nclx(nclx); - - return alpha_image; -} - - -void HeifContext::Image::set_preencoded_hevc_image(const std::vector& data) -{ - auto hvcC = std::make_shared(); - - - // --- parse the h265 stream and set hvcC headers and compressed image data - - int state = 0; - - bool first = true; - bool eof = false; - - int prev_start_code_start = -1; // init to an invalid value, will always be overwritten before use - int start_code_start; - int ptr = 0; - - for (;;) { - bool dump_nal = false; - - uint8_t c = data[ptr++]; - - if (state == 3) { - state = 0; - } - - if (c == 0 && state <= 1) { - state++; - } - else if (c == 0) { - // NOP - } - else if (c == 1 && state == 2) { - start_code_start = ptr - 3; - dump_nal = true; - state = 3; - } - else { - state = 0; - } - - if (ptr == (int) data.size()) { - start_code_start = (int) data.size(); - dump_nal = true; - eof = true; - } - - if (dump_nal) { - if (first) { - first = false; - } - else { - std::vector nal_data; - size_t length = start_code_start - (prev_start_code_start + 3); - - nal_data.resize(length); - - assert(prev_start_code_start >= 0); - memcpy(nal_data.data(), data.data() + prev_start_code_start + 3, length); - - int nal_type = (nal_data[0] >> 1); - - switch (nal_type) { - case 0x20: - case 0x21: - case 0x22: - hvcC->append_nal_data(nal_data); - break; - - default: { - std::vector nal_data_with_size; - nal_data_with_size.resize(nal_data.size() + 4); - - memcpy(nal_data_with_size.data() + 4, nal_data.data(), nal_data.size()); - nal_data_with_size[0] = ((nal_data.size() >> 24) & 0xFF); - nal_data_with_size[1] = ((nal_data.size() >> 16) & 0xFF); - nal_data_with_size[2] = ((nal_data.size() >> 8) & 0xFF); - nal_data_with_size[3] = ((nal_data.size() >> 0) & 0xFF); - - m_heif_context->m_heif_file->append_iloc_data(m_id, nal_data_with_size); - } - break; - } - } - - prev_start_code_start = start_code_start; - } - - if (eof) { - break; - } - } - - m_heif_context->m_heif_file->add_property(m_id, hvcC, true); -} - - -Error HeifContext::encode_image(const std::shared_ptr& pixel_image, - struct heif_encoder* encoder, - const struct heif_encoding_options& options, - enum heif_image_input_class input_class, - std::shared_ptr& out_image) -{ - Error error; - - // TODO: the hdlr box is not the right place for comments - // m_heif_file->set_hdlr_library_info(encoder->plugin->get_plugin_name()); - - switch (encoder->plugin->compression_format) { - case heif_compression_HEVC: { - error = encode_image_as_hevc(pixel_image, - encoder, - options, - input_class, - out_image); - } - break; - - case heif_compression_VVC: { - error = encode_image_as_vvc(pixel_image, - encoder, - options, - heif_image_input_class_normal, - out_image); - } - break; - - case heif_compression_AV1: { - error = encode_image_as_av1(pixel_image, - encoder, - options, - input_class, - out_image); - } - break; - - case heif_compression_JPEG2000: - case heif_compression_HTJ2K: { - error = encode_image_as_jpeg2000(pixel_image, - encoder, - options, - input_class, - out_image); - } - break; - - case heif_compression_JPEG: { - error = encode_image_as_jpeg(pixel_image, - encoder, - options, - input_class, - out_image); - } - break; - - case heif_compression_uncompressed: { - error = encode_image_as_uncompressed(pixel_image, - encoder, - options, - input_class, - out_image); - } - break; - - case heif_compression_mask: { - error = encode_image_as_mask(pixel_image, - encoder, - options, - input_class, - out_image); - } - break; - - default: - return Error(heif_error_Encoder_plugin_error, heif_suberror_Unsupported_codec); - } - - m_heif_file->set_brand(encoder->plugin->compression_format, - out_image->is_miaf_compatible()); - - return error; -} - -Error HeifContext::encode_grid(const std::vector>& tiles, - uint16_t rows, - uint16_t columns, - struct heif_encoder* encoder, - const struct heif_encoding_options& options, - std::shared_ptr& out_grid_image) -{ - // Create ImageGrid - ImageGrid grid; - grid.set_num_tiles(columns, rows); - int tile_width = tiles[0]->get_width(heif_channel_interleaved); - int tile_height = tiles[0]->get_height(heif_channel_interleaved); - grid.set_output_size(tile_width * columns, tile_height * rows); - std::vector grid_data = grid.write(); - - // Encode Tiles - Error error; - std::vector tile_ids; - for (int i=0; i out_tile; - error = encode_image(tiles[i], - encoder, - options, - heif_image_input_class_normal, - out_tile); - heif_item_id tile_id = out_tile->get_id(); - m_heif_file->get_infe_box(tile_id)->set_hidden_item(true); // only show the full grid - tile_ids.push_back(out_tile->get_id()); - } - - // Create Grid Item - heif_item_id grid_id = m_heif_file->add_new_image("grid"); - out_grid_image = std::make_shared(this, grid_id); - m_all_images.insert(std::make_pair(grid_id, out_grid_image)); - const int construction_method = 1; // 0=mdat 1=idat - m_heif_file->append_iloc_data(grid_id, grid_data, construction_method); - - // Connect tiles to grid - m_heif_file->add_iref_reference(grid_id, fourcc("dimg"), tile_ids); - - // Add ISPE property - int image_width = tile_width * columns; - int image_height = tile_height * rows; - m_heif_file->add_ispe_property(grid_id, image_width, image_height); - - // Set Brands - m_heif_file->set_brand(encoder->plugin->compression_format, - out_grid_image->is_miaf_compatible()); - - return error; -} - - -/* -static uint32_t get_rotated_width(heif_orientation orientation, uint32_t w, uint32_t h) -{ - return ((int)orientation) > 4 ? h : w; -} - - -static uint32_t get_rotated_height(heif_orientation orientation, uint32_t w, uint32_t h) -{ - return ((int)orientation) > 4 ? w : h; -} -*/ - -void HeifContext::write_image_metadata(std::shared_ptr src_image, int image_id) -{ - auto colorspace = src_image->get_colorspace(); - auto chroma = src_image->get_chroma_format(); - - - // --- write PIXI property - - if (colorspace == heif_colorspace_monochrome) { - m_heif_file->add_pixi_property(image_id, - src_image->get_bits_per_pixel(heif_channel_Y), 0, 0); - } - else if (colorspace == heif_colorspace_YCbCr) { - m_heif_file->add_pixi_property(image_id, - src_image->get_bits_per_pixel(heif_channel_Y), - src_image->get_bits_per_pixel(heif_channel_Cb), - src_image->get_bits_per_pixel(heif_channel_Cr)); - } - else if (colorspace == heif_colorspace_RGB) { - if (chroma == heif_chroma_444) { - m_heif_file->add_pixi_property(image_id, - src_image->get_bits_per_pixel(heif_channel_R), - src_image->get_bits_per_pixel(heif_channel_G), - src_image->get_bits_per_pixel(heif_channel_B)); - } - else if (chroma == heif_chroma_interleaved_RGB || - chroma == heif_chroma_interleaved_RGBA) { - m_heif_file->add_pixi_property(image_id, 8, 8, 8); - } - } - - - // --- write PASP property - - if (src_image->has_nonsquare_pixel_ratio()) { - auto pasp = std::make_shared(); - src_image->get_pixel_ratio(&pasp->hSpacing, &pasp->vSpacing); - - int index = m_heif_file->get_ipco_box()->find_or_append_child_box(pasp); - m_heif_file->get_ipma_box()->add_property_for_item_ID(image_id, Box_ipma::PropertyAssociation{false, uint16_t(index + 1)}); - } - - - // --- write CLLI property - - if (src_image->has_clli()) { - auto clli = std::make_shared(); - clli->clli = src_image->get_clli(); - - int index = m_heif_file->get_ipco_box()->find_or_append_child_box(clli); - m_heif_file->get_ipma_box()->add_property_for_item_ID(image_id, Box_ipma::PropertyAssociation{false, uint16_t(index + 1)}); - } - - - // --- write MDCV property - - if (src_image->has_mdcv()) { - auto mdcv = std::make_shared(); - mdcv->mdcv = src_image->get_mdcv(); - - int index = m_heif_file->get_ipco_box()->find_or_append_child_box(mdcv); - m_heif_file->get_ipma_box()->add_property_for_item_ID(image_id, Box_ipma::PropertyAssociation{false, uint16_t(index + 1)}); - } -} - - -static bool nclx_profile_matches_spec(heif_colorspace colorspace, - std::shared_ptr image_nclx, - const struct heif_color_profile_nclx* spec_nclx) -{ - if (colorspace != heif_colorspace_YCbCr) { - return true; - } - - // Do target specification -> always matches - if (!spec_nclx) { - return true; - } - - if (!image_nclx) { - // if no input nclx is specified, compare against default one - image_nclx = std::make_shared(); - } - - if (image_nclx->get_full_range_flag() != ( spec_nclx->full_range_flag == 0 ? false : true ) ) { - return false; - } - - if (image_nclx->get_matrix_coefficients() != spec_nclx->matrix_coefficients) { - return false; - } - - // TODO: are the colour primaries relevant for matrix-coefficients != 12,13 ? - // If not, we should skip this test for anything else than matrix-coefficients != 12,13. - if (image_nclx->get_colour_primaries() != spec_nclx->color_primaries) { - return false; - } - - return true; -} - - -static std::shared_ptr compute_target_nclx_profile(const std::shared_ptr& image, const heif_color_profile_nclx* output_nclx_profile) -{ - auto target_nclx_profile = std::make_shared(); - - // If there is an output NCLX specified, use that. - if (output_nclx_profile) { - target_nclx_profile->set_from_heif_color_profile_nclx(output_nclx_profile); - } - // Otherwise, if there is an input NCLX, keep that. - else if (auto input_nclx = image->get_color_profile_nclx()) { - *target_nclx_profile = *input_nclx; - } - // Otherwise, just use the defaults (set below) - else { - target_nclx_profile->set_undefined(); - } - - target_nclx_profile->replace_undefined_values_with_sRGB_defaults(); - - return target_nclx_profile; -} - - -Error HeifContext::encode_image_as_hevc(const std::shared_ptr& image, - struct heif_encoder* encoder, - const struct heif_encoding_options& options, - enum heif_image_input_class input_class, - std::shared_ptr& out_image) -{ - heif_item_id image_id = m_heif_file->add_new_image("hvc1"); - out_image = std::make_shared(this, image_id); - - - // --- check whether we have to convert the image color space - - heif_colorspace colorspace = image->get_colorspace(); - heif_chroma chroma = image->get_chroma_format(); - - auto target_nclx_profile = compute_target_nclx_profile(image, options.output_nclx_profile); - - if (encoder->plugin->plugin_api_version >= 2) { - encoder->plugin->query_input_colorspace2(encoder->encoder, &colorspace, &chroma); - } - else { - encoder->plugin->query_input_colorspace(&colorspace, &chroma); - } - - std::shared_ptr src_image; - if (colorspace != image->get_colorspace() || - chroma != image->get_chroma_format() || - !nclx_profile_matches_spec(colorspace, image->get_color_profile_nclx(), options.output_nclx_profile)) { - // @TODO: use color profile when converting - int output_bpp = 0; // same as input - src_image = convert_colorspace(image, colorspace, chroma, target_nclx_profile, - output_bpp, options.color_conversion_options); - if (!src_image) { - return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_color_conversion); - } - } - else { - src_image = image; - } - - - int input_width = src_image->get_width(heif_channel_Y); - int input_height = src_image->get_height(heif_channel_Y); - - out_image->set_size(input_width, input_height); - - - auto hvcC = std::make_shared(); - - heif_image c_api_image; - c_api_image.image = src_image; - - struct heif_error err = encoder->plugin->encode_image(encoder->encoder, &c_api_image, input_class); - if (err.code) { - return Error(err.code, - err.subcode, - err.message); - } - - int encoded_width = 0; - int encoded_height = 0; - - for (;;) { - uint8_t* data; - int size; - - encoder->plugin->get_compressed_data(encoder->encoder, &data, &size, NULL); - - if (data == NULL) { - break; - } - - - const uint8_t NAL_SPS = 33; - - if ((data[0] >> 1) == NAL_SPS) { - Box_hvcC::configuration config; - - parse_sps_for_hvcC_configuration(data, size, &config, &encoded_width, &encoded_height); - - hvcC->set_configuration(config); - } - - switch (data[0] >> 1) { - case 0x20: - case 0x21: - case 0x22: - hvcC->append_nal_data(data, size); - break; - - default: - m_heif_file->append_iloc_data_with_4byte_size(image_id, data, size); - } - } - - if (!encoded_width || !encoded_height) { - return Error(heif_error_Encoder_plugin_error, - heif_suberror_Invalid_image_size); - } - - m_heif_file->add_property(image_id, hvcC, true); - - if (encoder->plugin->plugin_api_version >= 3 && - encoder->plugin->query_encoded_size != nullptr) { - uint32_t check_encoded_width = input_width, check_encoded_height = input_height; - - encoder->plugin->query_encoded_size(encoder->encoder, - input_width, input_height, - &check_encoded_width, - &check_encoded_height); - - assert((int)check_encoded_width == encoded_width); - assert((int)check_encoded_height == encoded_height); - } - - - // Note: 'ispe' must be before the transformation properties - m_heif_file->add_ispe_property(image_id, encoded_width, encoded_height); - - // if image size was rounded up to even size, add a 'clap' box to crop the - // padding border away - - //uint32_t rotated_width = get_rotated_width(options.image_orientation, out_image->get_width(), out_image->get_height()); - //uint32_t rotated_height = get_rotated_height(options.image_orientation, out_image->get_width(), out_image->get_height()); - - if (input_width != encoded_width || - input_height != encoded_height) { - m_heif_file->add_clap_property(image_id, - input_width, - input_height, - encoded_width, - encoded_height); - - // MIAF 7.3.6.7 - // This is according to MIAF without Amd2. With Amd2, the restriction has been liften and the image is MIAF compatible. - // We might remove this code at a later point in time when MIAF Amd2 is in wide use. - - if (!is_integer_multiple_of_chroma_size(input_width, - input_height, - src_image->get_chroma_format())) { - out_image->mark_not_miaf_compatible(); - } - } - - m_heif_file->add_orientation_properties(image_id, options.image_orientation); - - // --- choose which color profile to put into 'colr' box - - if (input_class == heif_image_input_class_normal || input_class == heif_image_input_class_thumbnail) { - auto icc_profile = src_image->get_color_profile_icc(); - if (icc_profile) { - m_heif_file->set_color_profile(image_id, icc_profile); - } - - // save nclx profile - - bool save_nclx_profile = (options.output_nclx_profile != nullptr); - - // if there is an ICC profile, only save NCLX when we chose to save both profiles - if (icc_profile && !(options.version >= 3 && - options.save_two_colr_boxes_when_ICC_and_nclx_available)) { - save_nclx_profile = false; - } - - // we might have turned off nclx completely because macOS/iOS cannot read it - if (options.version >= 4 && options.macOS_compatibility_workaround_no_nclx_profile) { - save_nclx_profile = false; - } - - if (save_nclx_profile) { - m_heif_file->set_color_profile(image_id, target_nclx_profile); - } - } - - - write_image_metadata(src_image, image_id); - - m_top_level_images.push_back(out_image); - m_all_images[image_id] = out_image; - - - - // --- If there is an alpha channel, add it as an additional image. - // Save alpha after the color image because we need to know the final reference to the color image. - - if (options.save_alpha_channel && src_image->has_channel(heif_channel_Alpha)) { - - // --- generate alpha image - // TODO: can we directly code a monochrome image instead of the dummy color channels? - - std::shared_ptr alpha_image; - alpha_image = create_alpha_image_from_image_alpha_channel(src_image); - - - // --- encode the alpha image - - std::shared_ptr heif_alpha_image; - - Error error = encode_image_as_hevc(alpha_image, encoder, options, - heif_image_input_class_alpha, - heif_alpha_image); - if (error) { - return error; - } - - m_heif_file->add_iref_reference(heif_alpha_image->get_id(), fourcc("auxl"), {image_id}); - - if (src_image->is_premultiplied_alpha()) { - m_heif_file->add_iref_reference(image_id, fourcc("prem"), {heif_alpha_image->get_id()}); - } - - // TODO: MIAF says that the *:hevc:* urn is deprecated and we should use "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha" - // Is this compatible to other decoders? - m_heif_file->set_auxC_property(heif_alpha_image->get_id(), "urn:mpeg:hevc:2015:auxid:1"); - } - - - return Error::Ok; -} - - -Error HeifContext::encode_image_as_vvc(const std::shared_ptr& image, - struct heif_encoder* encoder, - const struct heif_encoding_options& options, - enum heif_image_input_class input_class, - std::shared_ptr& out_image) -{ - heif_item_id image_id = m_heif_file->add_new_image("vvc1"); - out_image = std::make_shared(this, image_id); - - - // --- check whether we have to convert the image color space - - heif_colorspace colorspace = image->get_colorspace(); - heif_chroma chroma = image->get_chroma_format(); - - auto target_nclx_profile = compute_target_nclx_profile(image, options.output_nclx_profile); - - if (encoder->plugin->plugin_api_version >= 2) { - encoder->plugin->query_input_colorspace2(encoder->encoder, &colorspace, &chroma); - } - else { - encoder->plugin->query_input_colorspace(&colorspace, &chroma); - } - - std::shared_ptr src_image; - if (colorspace != image->get_colorspace() || - chroma != image->get_chroma_format() || - !nclx_profile_matches_spec(colorspace, image->get_color_profile_nclx(), options.output_nclx_profile)) { - // @TODO: use color profile when converting - int output_bpp = 0; // same as input - src_image = convert_colorspace(image, colorspace, chroma, target_nclx_profile, - output_bpp, options.color_conversion_options); - if (!src_image) { - return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_color_conversion); - } - } - else { - src_image = image; - } - - - int input_width = src_image->get_width(heif_channel_Y); - int input_height = src_image->get_height(heif_channel_Y); - - out_image->set_size(input_width, input_height); - - - m_heif_file->add_vvcC_property(image_id); - - - heif_image c_api_image; - c_api_image.image = src_image; - - struct heif_error err = encoder->plugin->encode_image(encoder->encoder, &c_api_image, input_class); - if (err.code) { - return Error(err.code, - err.subcode, - err.message); - } - - int encoded_width = 0; - int encoded_height = 0; - - for (;;) { - uint8_t* data; - int size; - - encoder->plugin->get_compressed_data(encoder->encoder, &data, &size, NULL); - - if (data == NULL) { - break; - } - - - const uint8_t NAL_SPS = 15; - - uint8_t nal_type = 0; - if (size>=2) { - nal_type = (data[1] >> 3) & 0x1F; - } - - if (nal_type == NAL_SPS) { - Box_vvcC::configuration config; - - parse_sps_for_vvcC_configuration(data, size, &config, &encoded_width, &encoded_height); - - m_heif_file->set_vvcC_configuration(image_id, config); - } - - switch (data[0] >> 1) { - case 14: // VPS - case 15: // SPS - case 16: // PPS - m_heif_file->append_vvcC_nal_data(image_id, data, size); - break; - - default: - m_heif_file->append_iloc_data_with_4byte_size(image_id, data, size); - } - } - - if (!encoded_width || !encoded_height) { - return Error(heif_error_Encoder_plugin_error, - heif_suberror_Invalid_image_size); - } - - if (encoder->plugin->plugin_api_version >= 3 && - encoder->plugin->query_encoded_size != nullptr) { - uint32_t check_encoded_width = input_width, check_encoded_height = input_height; - - encoder->plugin->query_encoded_size(encoder->encoder, - input_width, input_height, - &check_encoded_width, - &check_encoded_height); - - assert((int)check_encoded_width == encoded_width); - assert((int)check_encoded_height == encoded_height); - } - - - // Note: 'ispe' must be before the transformation properties - m_heif_file->add_ispe_property(image_id, encoded_width, encoded_height); - - // if image size was rounded up to even size, add a 'clap' box to crop the - // padding border away - - //uint32_t rotated_width = get_rotated_width(options.image_orientation, out_image->get_width(), out_image->get_height()); - //uint32_t rotated_height = get_rotated_height(options.image_orientation, out_image->get_width(), out_image->get_height()); - - if (input_width != encoded_width || - input_height != encoded_height) { - m_heif_file->add_clap_property(image_id, - input_width, - input_height, - encoded_width, - encoded_height); - - // MIAF 7.3.6.7 - // This is according to MIAF without Amd2. With Amd2, the restriction has been liften and the image is MIAF compatible. - // We might remove this code at a later point in time when MIAF Amd2 is in wide use. - - if (!is_integer_multiple_of_chroma_size(input_width, - input_height, - src_image->get_chroma_format())) { - out_image->mark_not_miaf_compatible(); - } - } - - m_heif_file->add_orientation_properties(image_id, options.image_orientation); - - // --- choose which color profile to put into 'colr' box - - if (input_class == heif_image_input_class_normal || input_class == heif_image_input_class_thumbnail) { - auto icc_profile = src_image->get_color_profile_icc(); - if (icc_profile) { - m_heif_file->set_color_profile(image_id, icc_profile); - } - - // save nclx profile - - bool save_nclx_profile = (options.output_nclx_profile != nullptr); - - // if there is an ICC profile, only save NCLX when we chose to save both profiles - if (icc_profile && !(options.version >= 3 && - options.save_two_colr_boxes_when_ICC_and_nclx_available)) { - save_nclx_profile = false; - } - - // we might have turned off nclx completely because macOS/iOS cannot read it - if (options.version >= 4 && options.macOS_compatibility_workaround_no_nclx_profile) { - save_nclx_profile = false; - } - - if (save_nclx_profile) { - m_heif_file->set_color_profile(image_id, target_nclx_profile); - } - } - - - write_image_metadata(src_image, image_id); - - m_top_level_images.push_back(out_image); - m_all_images[image_id] = out_image; - - - - // --- If there is an alpha channel, add it as an additional image. - // Save alpha after the color image because we need to know the final reference to the color image. - - if (options.save_alpha_channel && src_image->has_channel(heif_channel_Alpha)) { - - // --- generate alpha image - // TODO: can we directly code a monochrome image instead of the dummy color channels? - - std::shared_ptr alpha_image; - alpha_image = create_alpha_image_from_image_alpha_channel(src_image); - - - // --- encode the alpha image - - std::shared_ptr heif_alpha_image; - - Error error = encode_image_as_vvc(alpha_image, encoder, options, - heif_image_input_class_alpha, - heif_alpha_image); - if (error) { - return error; - } - - m_heif_file->add_iref_reference(heif_alpha_image->get_id(), fourcc("auxl"), {image_id}); - - if (src_image->is_premultiplied_alpha()) { - m_heif_file->add_iref_reference(image_id, fourcc("prem"), {heif_alpha_image->get_id()}); - } - - // TODO: MIAF says that the *:hevc:* urn is deprecated and we should use "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha" - // Is this compatible to other decoders? - m_heif_file->set_auxC_property(heif_alpha_image->get_id(), "urn:mpeg:hevc:2015:auxid:1"); - } - - - return Error::Ok; -} - - -Error HeifContext::encode_image_as_av1(const std::shared_ptr& image, - struct heif_encoder* encoder, - const struct heif_encoding_options& options, - enum heif_image_input_class input_class, - std::shared_ptr& out_image) -{ - heif_item_id image_id = m_heif_file->add_new_image("av01"); - - out_image = std::make_shared(this, image_id); - m_top_level_images.push_back(out_image); - m_all_images[image_id] = out_image; - - // --- check whether we have to convert the image color space - - heif_colorspace colorspace = image->get_colorspace(); - heif_chroma chroma = image->get_chroma_format(); - - auto target_nclx_profile = compute_target_nclx_profile(image, options.output_nclx_profile); - - if (encoder->plugin->plugin_api_version >= 2) { - encoder->plugin->query_input_colorspace2(encoder->encoder, &colorspace, &chroma); - } - else { - encoder->plugin->query_input_colorspace(&colorspace, &chroma); - } - - std::shared_ptr src_image; - if (colorspace != image->get_colorspace() || - chroma != image->get_chroma_format() || - !nclx_profile_matches_spec(colorspace, image->get_color_profile_nclx(), options.output_nclx_profile)) { - // @TODO: use color profile when converting - int output_bpp = 0; // same as input - src_image = convert_colorspace(image, colorspace, chroma, target_nclx_profile, - output_bpp, options.color_conversion_options); - if (!src_image) { - return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_color_conversion); - } - } - else { - src_image = image; - } - - - // --- choose which color profile to put into 'colr' box - - if (input_class == heif_image_input_class_normal || input_class == heif_image_input_class_thumbnail) { - auto icc_profile = src_image->get_color_profile_icc(); - if (icc_profile) { - m_heif_file->set_color_profile(image_id, icc_profile); - } - - if (// target_nclx_profile && - (!icc_profile || (options.version >= 3 && - options.save_two_colr_boxes_when_ICC_and_nclx_available))) { - m_heif_file->set_color_profile(image_id, target_nclx_profile); - } - } - - - // --- if there is an alpha channel, add it as an additional image - - if (options.save_alpha_channel && src_image->has_channel(heif_channel_Alpha)) { - - // --- generate alpha image - // TODO: can we directly code a monochrome image instead of the dummy color channels? - - std::shared_ptr alpha_image; - alpha_image = create_alpha_image_from_image_alpha_channel(src_image); - - - // --- encode the alpha image - - std::shared_ptr heif_alpha_image; - - - Error error = encode_image_as_av1(alpha_image, encoder, options, - heif_image_input_class_alpha, - heif_alpha_image); - if (error) { - return error; - } - - m_heif_file->add_iref_reference(heif_alpha_image->get_id(), fourcc("auxl"), {image_id}); - m_heif_file->set_auxC_property(heif_alpha_image->get_id(), "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"); - - if (src_image->is_premultiplied_alpha()) { - m_heif_file->add_iref_reference(image_id, fourcc("prem"), {heif_alpha_image->get_id()}); - } - } - - Box_av1C::configuration config; - - // Fill preliminary av1C in case we cannot parse the sequence_header() correctly in the code below. - // TODO: maybe we can remove this later. - fill_av1C_configuration(&config, src_image); - - heif_image c_api_image; - c_api_image.image = src_image; - - struct heif_error err = encoder->plugin->encode_image(encoder->encoder, &c_api_image, input_class); - if (err.code) { - return Error(err.code, - err.subcode, - err.message); - } - - for (;;) { - uint8_t* data; - int size; - - encoder->plugin->get_compressed_data(encoder->encoder, &data, &size, nullptr); - - bool found_config = fill_av1C_configuration_from_stream(&config, data, size); - (void) found_config; - - if (data == nullptr) { - break; - } - - std::vector vec; - vec.resize(size); - memcpy(vec.data(), data, size); - - m_heif_file->append_iloc_data(image_id, vec); - } - - m_heif_file->add_av1C_property(image_id, config); - - uint32_t input_width, input_height; - input_width = src_image->get_width(); - input_height = src_image->get_height(); - - uint32_t encoded_width = input_width, encoded_height = input_height; + ImageGrid grid; + err = grid.parse(grid_data); + if (err) { + return false; + } - if (encoder->plugin->plugin_api_version >= 3 && - encoder->plugin->query_encoded_size != nullptr) { - encoder->plugin->query_encoded_size(encoder->encoder, - input_width, input_height, - &encoded_width, - &encoded_height); - } - // Note: 'ispe' must be before the transformation properties - m_heif_file->add_ispe_property(image_id, encoded_width, encoded_height); + auto iref_box = m_heif_file->get_iref_box(); - if (input_width != encoded_width || - input_height != encoded_height) { - m_heif_file->add_clap_property(image_id, input_width, input_height, - encoded_width, encoded_height); + if (!iref_box) { + return false; + } - // According to MIAF without Amd2, an image is required to be cropped to multiples of the chroma format raster. - // However, since AVIF is based on MIAF, the whole image would be invalid in that case. - // As this restriction was lifted with MIAF-Amd2, we include the MIAF brand for all AVIF images. + std::vector image_references = iref_box->get_references(ID, fourcc("dimg")); - /* - if (!is_integer_multiple_of_chroma_size(input_width, - input_height, - src_image->get_chroma_format())) { - out_image->mark_not_miaf_compatible(); + if ((int) image_references.size() != grid.get_rows() * grid.get_columns()) { + return false; } - */ - m_heif_file->add_orientation_properties(image_id, options.image_orientation); - } + // --- check that all image IDs are valid images + for (heif_item_id tile_id : image_references) { + if (!is_image(tile_id)) { + return false; + } + } - write_image_metadata(src_image, image_id); + // --- check whether at least one tile has an alpha channel - return Error::Ok; -} + bool has_alpha = false; -Error HeifContext::encode_image_as_jpeg2000(const std::shared_ptr& image, - struct heif_encoder* encoder, - const struct heif_encoding_options& options, - enum heif_image_input_class input_class, - std::shared_ptr& out_image) { + for (heif_item_id tile_id : image_references) { + auto iter = m_all_images.find(tile_id); + if (iter == m_all_images.end()) { + return false; + } - heif_item_id image_id = m_heif_file->add_new_image("j2k1"); + const std::shared_ptr tileImg = iter->second; - out_image = std::make_shared(this, image_id); - m_top_level_images.push_back(out_image); + has_alpha |= tileImg->get_alpha_channel() != nullptr; + } + return has_alpha; + } + else { + // TODO: what about overlays ? + return false; + } +} - // TODO: simplify the color-conversion part. It's the same for each codec. - // ---begin--- - heif_colorspace colorspace = image->get_colorspace(); - heif_chroma chroma = image->get_chroma_format(); - /* - auto color_profile = image->get_color_profile_nclx(); - if (!color_profile) { - color_profile = std::make_shared(); - } - auto nclx_profile = std::dynamic_pointer_cast(color_profile); -*/ +Error HeifContext::get_id_of_non_virtual_child_image(heif_item_id id, heif_item_id& out) const +{ + uint32_t image_type = m_heif_file->get_item_type_4cc(id); + if (image_type == fourcc("grid") || + image_type == fourcc("iden") || + image_type == fourcc("iovl")) { + auto iref_box = m_heif_file->get_iref_box(); + if (!iref_box) { + return Error(heif_error_Invalid_input, + heif_suberror_No_item_data, + "Derived image does not reference any other image items"); + } - auto target_nclx_profile = compute_target_nclx_profile(image, options.output_nclx_profile); + std::vector image_references = iref_box->get_references(id, fourcc("dimg")); - if (encoder->plugin->plugin_api_version >= 2) { - encoder->plugin->query_input_colorspace2(encoder->encoder, &colorspace, &chroma); - } - else { - encoder->plugin->query_input_colorspace(&colorspace, &chroma); - } + // TODO: check whether this really can be recursive (e.g. overlay of grid images) - std::shared_ptr src_image; - if (colorspace != image->get_colorspace() || - chroma != image->get_chroma_format() || - !nclx_profile_matches_spec(colorspace, image->get_color_profile_nclx(), options.output_nclx_profile)) { - int output_bpp = 0; // same as input - src_image = convert_colorspace(image, colorspace, chroma, target_nclx_profile, - output_bpp, options.color_conversion_options); - if (!src_image) { - return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_color_conversion); + if (image_references.empty() || image_references[0] == id) { + return Error(heif_error_Invalid_input, + heif_suberror_No_item_data, + "Derived image does not reference any other image items"); + } + else { + return get_id_of_non_virtual_child_image(image_references[0], out); } } else { - src_image = image; + out = id; + return Error::Ok; } - // ---end--- +} - // --- if there is an alpha channel, add it as an additional image +Result> HeifContext::decode_image(heif_item_id ID, + heif_colorspace out_colorspace, + heif_chroma out_chroma, + const struct heif_decoding_options& options, + bool decode_only_tile, uint32_t tx, uint32_t ty) const +{ + std::shared_ptr imgitem; + if (m_all_images.find(ID) != m_all_images.end()) { + imgitem = m_all_images.find(ID)->second; + } - if (options.save_alpha_channel && src_image->has_channel(heif_channel_Alpha)) { + // Note: this may happen, for example when an 'iden' image references a non-existing image item. + if (imgitem == nullptr) { + return Error(heif_error_Invalid_input, heif_suberror_Nonexisting_item_referenced); + } - // --- generate alpha image - // TODO: can we directly code a monochrome image instead of the dummy color channels? - std::shared_ptr alpha_image; - alpha_image = create_alpha_image_from_image_alpha_channel(src_image); + auto decodingResult = imgitem->decode_image(options, decode_only_tile, tx, ty); + if (decodingResult.error) { + return decodingResult.error; + } + std::shared_ptr img = decodingResult.value; - // --- encode the alpha image - std::shared_ptr heif_alpha_image; + // --- convert to output chroma format + heif_colorspace target_colorspace = (out_colorspace == heif_colorspace_undefined ? + img->get_colorspace() : + out_colorspace); - Error error = encode_image_as_jpeg2000(alpha_image, encoder, options, - heif_image_input_class_alpha, - heif_alpha_image); - if (error) { - return error; - } + heif_chroma target_chroma = (out_chroma == heif_chroma_undefined ? + img->get_chroma_format() : out_chroma); + + bool different_chroma = (target_chroma != img->get_chroma_format()); + bool different_colorspace = (target_colorspace != img->get_colorspace()); - m_heif_file->add_iref_reference(heif_alpha_image->get_id(), fourcc("auxl"), {image_id}); - m_heif_file->set_auxC_property(heif_alpha_image->get_id(), "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"); + int bpp = options.convert_hdr_to_8bit ? 8 : 0; + // TODO: check BPP changed + if (different_chroma || different_colorspace) { - if (src_image->is_premultiplied_alpha()) { - m_heif_file->add_iref_reference(image_id, fourcc("prem"), {heif_alpha_image->get_id()}); + img = convert_colorspace(img, target_colorspace, target_chroma, nullptr, bpp, options.color_conversion_options); + if (!img) { + return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_color_conversion); } } + img->add_warnings(imgitem->get_decoding_warnings()); - //Encode Image - heif_image c_api_image; - c_api_image.image = src_image; - encoder->plugin->encode_image(encoder->encoder, &c_api_image, input_class); - - //Get Compressed Data - for (;;) { - uint8_t* data; - int size; + return img; +} - encoder->plugin->get_compressed_data(encoder->encoder, &data, &size, nullptr); - if (data == NULL) { - break; - } +static std::shared_ptr +create_alpha_image_from_image_alpha_channel(const std::shared_ptr& image) +{ + // --- generate alpha image - std::vector vec; - vec.resize(size); - memcpy(vec.data(), data, size); + std::shared_ptr alpha_image = std::make_shared(); + alpha_image->create(image->get_width(), image->get_height(), + heif_colorspace_monochrome, heif_chroma_monochrome); - m_heif_file->append_iloc_data(image_id, vec); + if (image->has_channel(heif_channel_Alpha)) { + alpha_image->copy_new_plane_from(image, heif_channel_Alpha, heif_channel_Y); } + else if (image->get_chroma_format() == heif_chroma_interleaved_RGBA) { + alpha_image->extract_alpha_from_RGBA(image); + } + // TODO: 16 bit + // --- set nclx profile with full-range flag + auto nclx = std::make_shared(); + nclx->set_undefined(); + nclx->set_full_range_flag(true); // this is the default, but just to be sure in case the defaults change + alpha_image->set_color_profile_nclx(nclx); - //Add 'ispe' Property - m_heif_file->add_ispe_property(image_id, image->get_width(), image->get_height()); - - //Add 'colr' Property - m_heif_file->set_color_profile(image_id, target_nclx_profile); + return alpha_image; +} - //Add 'j2kH' Property - auto j2kH = m_heif_file->add_j2kH_property(image_id); - //Add 'cdef' to 'j2kH' - auto cdef = std::make_shared(); - cdef->set_channels(src_image->get_colorspace()); - j2kH->append_child_box(cdef); +Error HeifContext::encode_image(const std::shared_ptr& pixel_image, + struct heif_encoder* encoder, + const struct heif_encoding_options& in_options, + enum heif_image_input_class input_class, + std::shared_ptr& out_image) +{ + Error error; - write_image_metadata(src_image, image_id); - return Error::Ok; -} + std::shared_ptr image_item = ImageItem::alloc_for_compression_format(this, encoder->plugin->compression_format); -static uint8_t JPEG_SOS = 0xDA; +#if 0 + // TODO: the hdlr box is not the right place for comments + // m_heif_file->set_hdlr_library_info(encoder->plugin->get_plugin_name()); -// returns 0 if the marker_type was not found -size_t find_jpeg_marker_start(const std::vector& data, uint8_t marker_type) -{ - for (size_t i = 0; i < data.size() - 1; i++) { - if (data[i]==0xFF && data[i+1]==marker_type) { - return i; + case heif_compression_mask: { + error = encode_image_as_mask(pixel_image, + encoder, + options, + input_class, + out_image); } - } - - return 0; -} - + break; -Error HeifContext::encode_image_as_jpeg(const std::shared_ptr& image, - struct heif_encoder* encoder, - const struct heif_encoding_options& options, - enum heif_image_input_class input_class, - std::shared_ptr& out_image) -{ - heif_item_id image_id = m_heif_file->add_new_image("jpeg"); + default: + return Error(heif_error_Encoder_plugin_error, heif_suberror_Unsupported_codec); + } +#endif - out_image = std::make_shared(this, image_id); - m_top_level_images.push_back(out_image); - m_all_images[image_id] = out_image; // --- check whether we have to convert the image color space - heif_colorspace colorspace = image->get_colorspace(); - heif_chroma chroma = image->get_chroma_format(); - - // JPEG always uses CCIR-601 + // The reason for doing the color conversion here is that the input might be an RGBA image and the color conversion + // will extract the alpha plane anyway. We can reuse that plane below instead of having to do a new conversion. - heif_color_profile_nclx target_heif_nclx; - target_heif_nclx.matrix_coefficients = heif_matrix_coefficients_ITU_R_BT_601_6; - target_heif_nclx.color_primaries = heif_color_primaries_ITU_R_BT_601_6; - target_heif_nclx.transfer_characteristics = heif_transfer_characteristic_ITU_R_BT_601_6; - target_heif_nclx.full_range_flag = true; + heif_encoding_options options = in_options; - auto target_nclx_profile = std::make_shared(); - target_nclx_profile->set_from_heif_color_profile_nclx(&target_heif_nclx); - - if (encoder->plugin->plugin_api_version >= 2) { - encoder->plugin->query_input_colorspace2(encoder->encoder, &colorspace, &chroma); - } - else { - encoder->plugin->query_input_colorspace(&colorspace, &chroma); + if (const auto* nclx = image_item->get_forced_output_nclx()) { + options.output_nclx_profile = nclx; } - std::shared_ptr src_image; - if (colorspace != image->get_colorspace() || - chroma != image->get_chroma_format() || - !nclx_profile_matches_spec(colorspace, image->get_color_profile_nclx(), &target_heif_nclx)) { - // @TODO: use color profile when converting - int output_bpp = 0; // same as input - src_image = convert_colorspace(image, colorspace, chroma, target_nclx_profile, - output_bpp, options.color_conversion_options); - if (!src_image) { - return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_color_conversion); - } - } - else { - src_image = image; + Result> srcImageResult = image_item->convert_colorspace_for_encoding(pixel_image, + encoder, + options); + if (srcImageResult.error) { + return srcImageResult.error; } + std::shared_ptr colorConvertedImage = srcImageResult.value; - // --- choose which color profile to put into 'colr' box - - if (input_class == heif_image_input_class_normal || input_class == heif_image_input_class_thumbnail) { - auto icc_profile = src_image->get_color_profile_icc(); - if (icc_profile) { - m_heif_file->set_color_profile(image_id, icc_profile); - } - if (// target_nclx_profile && - (!icc_profile || (options.version >= 3 && - options.save_two_colr_boxes_when_ICC_and_nclx_available))) { - m_heif_file->set_color_profile(image_id, target_nclx_profile); - } + Error err = image_item->encode_to_item(this, + colorConvertedImage, + encoder, options, input_class); + if (err) { + return err; } + out_image = image_item; + + insert_image_item(image_item->get_id(), image_item); + // --- if there is an alpha channel, add it as an additional image - if (options.save_alpha_channel && src_image->has_channel(heif_channel_Alpha)) { + if (options.save_alpha_channel && + colorConvertedImage->has_alpha() && + image_item->get_auxC_alpha_channel_type() != nullptr) { // does not need a separate alpha aux image // --- generate alpha image // TODO: can we directly code a monochrome image instead of the dummy color channels? std::shared_ptr alpha_image; - alpha_image = create_alpha_image_from_image_alpha_channel(src_image); + alpha_image = create_alpha_image_from_image_alpha_channel(colorConvertedImage); // --- encode the alpha image - std::shared_ptr heif_alpha_image; + std::shared_ptr heif_alpha_image; - - Error error = encode_image_as_jpeg(alpha_image, encoder, options, - heif_image_input_class_alpha, - heif_alpha_image); + error = encode_image(alpha_image, encoder, options, + heif_image_input_class_alpha, + heif_alpha_image); if (error) { return error; } - m_heif_file->add_iref_reference(heif_alpha_image->get_id(), fourcc("auxl"), {image_id}); - m_heif_file->set_auxC_property(heif_alpha_image->get_id(), "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"); - - if (src_image->is_premultiplied_alpha()) { - m_heif_file->add_iref_reference(image_id, fourcc("prem"), {heif_alpha_image->get_id()}); - } - } - - heif_image c_api_image; - c_api_image.image = src_image; - - struct heif_error err = encoder->plugin->encode_image(encoder->encoder, &c_api_image, input_class); - if (err.code) { - return Error(err.code, - err.subcode, - err.message); - } - - std::vector vec; - - for (;;) { - uint8_t* data; - int size; - - encoder->plugin->get_compressed_data(encoder->encoder, &data, &size, nullptr); - - if (data == nullptr) { - break; - } - - size_t oldsize = vec.size(); - vec.resize(oldsize + size); - memcpy(vec.data() + oldsize, data, size); - } - - // Optional: split the JPEG data into a jpgC box and the actual image data. - // Currently disabled because not supported yet in other decoders. - if (false) { - size_t pos = find_jpeg_marker_start(vec, JPEG_SOS); - if (pos > 0) { - std::vector jpgC_data(vec.begin(), vec.begin() + pos); - auto jpgC = std::make_shared(); - jpgC->set_data(jpgC_data); - - auto ipma_box = m_heif_file->get_ipma_box(); - int index = m_heif_file->get_ipco_box()->find_or_append_child_box(jpgC); - ipma_box->add_property_for_item_ID(image_id, Box_ipma::PropertyAssociation{true, uint16_t(index + 1)}); - - std::vector image_data(vec.begin() + pos, vec.end()); - vec = std::move(image_data); - } - } - - m_heif_file->append_iloc_data(image_id, vec); - -#if 0 - // TODO: extract 'jpgC' header data -#endif - - uint32_t input_width, input_height; - input_width = src_image->get_width(); - input_height = src_image->get_height(); - - // Note: 'ispe' must be before the transformation properties - m_heif_file->add_ispe_property(image_id, input_width, input_height); - - uint32_t encoded_width = input_width, encoded_height = input_height; - - if (encoder->plugin->plugin_api_version >= 3 && - encoder->plugin->query_encoded_size != nullptr) { - - encoder->plugin->query_encoded_size(encoder->encoder, - input_width, input_height, - &encoded_width, - &encoded_height); - } - - if (input_width != encoded_width || - input_height != encoded_height) { - m_heif_file->add_clap_property(image_id, input_width, input_height, - encoded_width, encoded_height); - - // MIAF 7.3.6.7 - // This is according to MIAF without Amd2. With Amd2, the restriction has been liften and the image is MIAF compatible. - // We might remove this code at a later point in time when MIAF Amd2 is in wide use. + m_heif_file->add_iref_reference(heif_alpha_image->get_id(), fourcc("auxl"), {out_image->get_id()}); + m_heif_file->set_auxC_property(heif_alpha_image->get_id(), out_image->get_auxC_alpha_channel_type()); - if (!is_integer_multiple_of_chroma_size(input_width, - input_height, - src_image->get_chroma_format())) { - out_image->mark_not_miaf_compatible(); + if (pixel_image->is_premultiplied_alpha()) { + m_heif_file->add_iref_reference(out_image->get_id(), fourcc("prem"), {heif_alpha_image->get_id()}); } } - m_heif_file->add_orientation_properties(image_id, options.image_orientation); - - write_image_metadata(src_image, image_id); - - return Error::Ok; -} - -Error HeifContext::encode_image_as_uncompressed(const std::shared_ptr& src_image, - struct heif_encoder* encoder, - const struct heif_encoding_options& options, - enum heif_image_input_class input_class, - std::shared_ptr& out_image) -{ -#if WITH_UNCOMPRESSED_CODEC - heif_item_id image_id = m_heif_file->add_new_image("unci"); - out_image = std::make_shared(this, image_id); - - Error err = UncompressedImageCodec::encode_uncompressed_image(m_heif_file, - src_image, - encoder->encoder, - options, - out_image); - - m_top_level_images.push_back(out_image); - m_all_images[image_id] = out_image; -#endif - //write_image_metadata(src_image, image_id); - - return Error::Ok; -} - + m_heif_file->set_brand(encoder->plugin->compression_format, + out_image->is_miaf_compatible()); -Error HeifContext::encode_image_as_mask(const std::shared_ptr& src_image, - struct heif_encoder* encoder, - const struct heif_encoding_options& options, - enum heif_image_input_class input_class, - std::shared_ptr& out_image) -{ - heif_item_id image_id = m_heif_file->add_new_hidden_image("mski"); - out_image = std::make_shared(this, image_id); - Error err = MaskImageCodec::encode_mask_image(m_heif_file, - src_image, - encoder->encoder, - options, - out_image); - m_top_level_images.push_back(out_image); - m_all_images[image_id] = out_image; - write_image_metadata(src_image, image_id); - return Error::Ok; + return error; } - -void HeifContext::set_primary_image(const std::shared_ptr& image) +void HeifContext::set_primary_image(const std::shared_ptr& image) { // update heif context @@ -3607,23 +1234,8 @@ void HeifContext::set_primary_image(const std::shared_ptr& image) } -Error HeifContext::set_primary_item(heif_item_id id) -{ - auto iter = m_all_images.find(id); - if (iter == m_all_images.end()) { - return Error(heif_error_Usage_error, - heif_suberror_No_or_invalid_primary_item, - "Cannot set primary item as the ID does not exist."); - } - - set_primary_image(iter->second); - - return Error::Ok; -} - - -Error HeifContext::assign_thumbnail(const std::shared_ptr& master_image, - const std::shared_ptr& thumbnail_image) +Error HeifContext::assign_thumbnail(const std::shared_ptr& master_image, + const std::shared_ptr& thumbnail_image) { m_heif_file->add_iref_reference(thumbnail_image->get_id(), fourcc("thmb"), {master_image->get_id()}); @@ -3636,7 +1248,7 @@ Error HeifContext::encode_thumbnail(const std::shared_ptr& image struct heif_encoder* encoder, const struct heif_encoding_options& options, int bbox_size, - std::shared_ptr& out_thumbnail_handle) + std::shared_ptr& out_thumbnail_handle) { Error error; @@ -3685,7 +1297,7 @@ Error HeifContext::encode_thumbnail(const std::shared_ptr& image } -Error HeifContext::add_exif_metadata(const std::shared_ptr& master_image, const void* data, int size) +Error HeifContext::add_exif_metadata(const std::shared_ptr& master_image, const void* data, int size) { // find location of TIFF header uint32_t offset = 0; @@ -3714,19 +1326,19 @@ Error HeifContext::add_exif_metadata(const std::shared_ptr& master_image, return add_generic_metadata(master_image, data_array.data(), (int) data_array.size(), - "Exif", nullptr, nullptr, heif_metadata_compression_off, nullptr); + fourcc("Exif"), nullptr, nullptr, heif_metadata_compression_off, nullptr); } -Error HeifContext::add_XMP_metadata(const std::shared_ptr& master_image, const void* data, int size, +Error HeifContext::add_XMP_metadata(const std::shared_ptr& master_image, const void* data, int size, heif_metadata_compression compression) { - return add_generic_metadata(master_image, data, size, "mime", "application/rdf+xml", nullptr, compression, nullptr); + return add_generic_metadata(master_image, data, size, fourcc("mime"), "application/rdf+xml", nullptr, compression, nullptr); } -Error HeifContext::add_generic_metadata(const std::shared_ptr& master_image, const void* data, int size, - const char* item_type, const char* content_type, const char* item_uri_type, heif_metadata_compression compression, +Error HeifContext::add_generic_metadata(const std::shared_ptr& master_image, const void* data, int size, + uint32_t item_type, const char* content_type, const char* item_uri_type, heif_metadata_compression compression, heif_item_id* out_item_id) { // create an infe box describing what kind of data we are storing (this also creates a new ID) @@ -3757,15 +1369,24 @@ Error HeifContext::add_generic_metadata(const std::shared_ptr& master_ima // only set metadata compression for MIME type data which has 'content_encoding' field if (compression != heif_metadata_compression_off && - strcmp(item_type, "mime") != 0) { + item_type != fourcc("mime")) { // TODO: error, compression not supported } std::vector data_array; - if (compression == heif_metadata_compression_deflate) { -#if WITH_DEFLATE_HEADER_COMPRESSION - data_array = deflate((const uint8_t*) data, size); + if (compression == heif_metadata_compression_zlib) { +#if HAVE_ZLIB + data_array = compress_zlib((const uint8_t*) data, size); + metadata_infe_box->set_content_encoding("compress_zlib"); +#else + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_header_compression_method); +#endif + } + else if (compression == heif_metadata_compression_deflate) { +#if HAVE_ZLIB + data_array = compress_zlib((const uint8_t*) data, size); metadata_infe_box->set_content_encoding("deflate"); #else return Error(heif_error_Unsupported_feature, @@ -3781,7 +1402,7 @@ Error HeifContext::add_generic_metadata(const std::shared_ptr& master_ima // copy the data into the file, store the pointer to it in an iloc box entry - m_heif_file->append_iloc_data(metadata_id, data_array); + m_heif_file->append_iloc_data(metadata_id, data_array, 0); return Error::Ok; } @@ -3793,3 +1414,94 @@ heif_property_id HeifContext::add_property(heif_item_id targetItem, std::shared_ return id; } + + +Result HeifContext::add_pyramid_group(const std::vector& layer_item_ids) +{ + struct pymd_entry + { + std::shared_ptr item; + uint32_t width = 0; + }; + + // --- sort all images by size + + std::vector pymd_entries; + for (auto id : layer_item_ids) { + auto image_item = get_image(id, true); + if (auto error = image_item->get_item_error()) { + return error; + } + + pymd_entry entry; + entry.item = image_item; + entry.width = image_item->get_width(); + pymd_entries.emplace_back(entry); + } + + std::sort(pymd_entries.begin(), pymd_entries.end(), [](const pymd_entry& a, const pymd_entry& b) { + return a.width < b.width; + }); + + + // --- generate pymd box + + auto pymd = std::make_shared(); + std::vector layers; + std::vector ids; + + auto base_item = pymd_entries.back().item; + + uint32_t tile_w=0, tile_h=0; + base_item->get_tile_size(tile_w, tile_h); + + uint32_t last_width=0, last_height=0; + + for (const auto& entry : pymd_entries) { + auto layer_item = entry.item; + + if (false) { + // according to pymd definition, we should check that all layers have the same tile size + uint32_t item_tile_w = 0, item_tile_h = 0; + base_item->get_tile_size(item_tile_w, item_tile_h); + if (item_tile_w != tile_w || item_tile_h != tile_h) { + // TODO: add warning that tile sizes are not the same + } + } + + heif_image_tiling tiling = layer_item->get_heif_image_tiling(); + + if (tiling.image_width < last_width || tiling.image_height < last_height) { + return Error{ + heif_error_Invalid_input, + heif_suberror_Invalid_parameter_value, + "Multi-resolution pyramid images have to be provided ordered from smallest to largest." + }; + } + + last_width = tiling.image_width; + last_height = tiling.image_height; + + Box_pymd::LayerInfo layer{}; + layer.layer_binning = (uint16_t)(base_item->get_width() / tiling.image_width); + layer.tiles_in_layer_row_minus1 = static_cast(tiling.num_rows - 1); + layer.tiles_in_layer_column_minus1 = static_cast(tiling.num_columns - 1); + layers.push_back(layer); + ids.push_back(layer_item->get_id()); + } + + heif_item_id group_id = m_heif_file->get_unused_item_id(); + + pymd->set_group_id(group_id); + pymd->set_layers((uint16_t)tile_w, (uint16_t)tile_h, layers, ids); + + m_heif_file->add_entity_group_box(pymd); + + // add back-references to base image + + for (size_t i = 0; i < ids.size() - 1; i++) { + m_heif_file->add_iref_reference(ids[i], fourcc("base"), {ids.back()}); + } + + return {group_id}; +} diff --git a/libheif/context.h b/libheif/context.h index 3928d53937..a1da9027cf 100644 --- a/libheif/context.h +++ b/libheif/context.h @@ -31,6 +31,7 @@ #include "error.h" #include "libheif/heif.h" +#include "libheif/heif_experimental.h" #include "libheif/heif_plugin.h" #include "bitstream.h" @@ -38,24 +39,13 @@ #include "region.h" -class HeifContext; - class HeifFile; class HeifPixelImage; class StreamWriter; - -class ImageMetadata -{ -public: - heif_item_id item_id; - std::string item_type; // e.g. "Exif" - std::string content_type; - std::string item_uri_type; - std::vector m_data; -}; +class ImageItem; // This is a higher-level view than HeifFile. @@ -70,334 +60,59 @@ class HeifContext : public ErrorBuffer void set_max_decoding_threads(int max_threads) { m_max_decoding_threads = max_threads; } - void set_maximum_image_size_limit(int maximum_size) - { - m_maximum_image_width_limit = maximum_size; - m_maximum_image_height_limit = maximum_size; - } - - Error read(const std::shared_ptr& reader); - - Error read_from_file(const char* input_filename); - - Error read_from_memory(const void* data, size_t size, bool copy); - - class Image : public ErrorBuffer - { - public: - Image(HeifContext* file, heif_item_id id); - - ~Image(); - - void clear() - { - m_thumbnails.clear(); - m_alpha_channel.reset(); - m_depth_channel.reset(); - m_aux_images.clear(); - } - - void set_resolution(int w, int h) - { - m_width = w; - m_height = h; - } - - void set_primary(bool flag = true) { m_is_primary = flag; } - - heif_item_id get_id() const { return m_id; } - - //void set_id(heif_item_id id) { m_id=id; } (already set in constructor) - - int get_width() const { return m_width; } - - int get_height() const { return m_height; } - - int get_ispe_width() const; - - int get_ispe_height() const; - - int get_luma_bits_per_pixel() const; - - int get_chroma_bits_per_pixel() const; - - Error get_preferred_decoding_colorspace(heif_colorspace* out_colorspace, heif_chroma* out_chroma) const; - - bool is_primary() const { return m_is_primary; } - - void set_size(int w, int h) - { - m_width = w; - m_height = h; - } - - - // -- thumbnails - - void set_is_thumbnail() - { - m_is_thumbnail = true; - } - - void add_thumbnail(const std::shared_ptr& img) { m_thumbnails.push_back(img); } - - bool is_thumbnail() const { return m_is_thumbnail; } - - const std::vector>& get_thumbnails() const { return m_thumbnails; } - - - // --- alpha channel - - void set_is_alpha_channel() - { - m_is_alpha_channel = true; - } - - void set_alpha_channel(std::shared_ptr img) { m_alpha_channel = std::move(img); } - - bool is_alpha_channel() const { return m_is_alpha_channel; } - - const std::shared_ptr& get_alpha_channel() const { return m_alpha_channel; } - - void set_is_premultiplied_alpha(bool flag) { m_premultiplied_alpha = flag; } - - bool is_premultiplied_alpha() const { return m_premultiplied_alpha; } - - - // --- depth channel - - void set_is_depth_channel() - { - m_is_depth_channel = true; - } - - void set_depth_channel(std::shared_ptr img) { m_depth_channel = std::move(img); } - - bool is_depth_channel() const { return m_is_depth_channel; } - - const std::shared_ptr& get_depth_channel() const { return m_depth_channel; } - - - void set_depth_representation_info(struct heif_depth_representation_info& info) - { - m_has_depth_representation_info = true; - m_depth_representation_info = info; - } - - bool has_depth_representation_info() const - { - return m_has_depth_representation_info; - } - - const struct heif_depth_representation_info& get_depth_representation_info() const - { - return m_depth_representation_info; - } - - - // --- generic aux image - - void set_is_aux_image(const std::string& aux_type) - { - m_is_aux_image = true; - m_aux_image_type = aux_type; - } - - void add_aux_image(std::shared_ptr img) { m_aux_images.push_back(std::move(img)); } - - bool is_aux_image() const { return m_is_aux_image; } + int get_max_decoding_threads() const { return m_max_decoding_threads; } - const std::string& get_aux_type() const { return m_aux_image_type; } + void set_security_limits(const heif_security_limits* limits); - std::vector> get_aux_images(int aux_image_filter = 0) const - { - if (aux_image_filter == 0) { - return m_aux_images; - } - else { - std::vector> auxImgs; - for (const auto& aux : m_aux_images) { - if ((aux_image_filter & LIBHEIF_AUX_IMAGE_FILTER_OMIT_ALPHA) && aux->is_alpha_channel()) { - continue; - } + [[nodiscard]] heif_security_limits* get_security_limits() { return &m_limits; } - if ((aux_image_filter & LIBHEIF_AUX_IMAGE_FILTER_OMIT_DEPTH) && - aux->is_depth_channel()) { - continue; - } + [[nodiscard]] const heif_security_limits* get_security_limits() const { return &m_limits; } - auxImgs.push_back(aux); - } - - return auxImgs; - } - } - - - // --- metadata - - void add_metadata(std::shared_ptr metadata) - { - m_metadata.push_back(std::move(metadata)); - } - - const std::vector>& get_metadata() const { return m_metadata; } - - - // --- miaf - - void mark_not_miaf_compatible() { m_miaf_compatible = false; } - - bool is_miaf_compatible() const { return m_miaf_compatible; } - - - // === writing === - - void set_preencoded_hevc_image(const std::vector& data); - - const std::shared_ptr& get_color_profile_nclx() const { return m_color_profile_nclx; } - - const std::shared_ptr& get_color_profile_icc() const { return m_color_profile_icc; } - - void set_color_profile(const std::shared_ptr& profile) - { - auto icc = std::dynamic_pointer_cast(profile); - if (icc) { - m_color_profile_icc = std::move(icc); - } - - auto nclx = std::dynamic_pointer_cast(profile); - if (nclx) { - m_color_profile_nclx = std::move(nclx); - } - }; - - void set_intrinsic_matrix(const Box_cmin::RelativeIntrinsicMatrix& cmin) { - m_has_intrinsic_matrix = true; - m_intrinsic_matrix = cmin.to_absolute(get_ispe_width(), get_ispe_height()); - } - - bool has_intrinsic_matrix() const { return m_has_intrinsic_matrix; } - - Box_cmin::AbsoluteIntrinsicMatrix& get_intrinsic_matrix() { return m_intrinsic_matrix; } - - const Box_cmin::AbsoluteIntrinsicMatrix& get_intrinsic_matrix() const { return m_intrinsic_matrix; } - - - void set_extrinsic_matrix(const Box_cmex::ExtrinsicMatrix& cmex) { - m_has_extrinsic_matrix = true; - m_extrinsic_matrix = cmex; - } - - bool has_extrinsic_matrix() const { return m_has_extrinsic_matrix; } - - Box_cmex::ExtrinsicMatrix& get_extrinsic_matrix() { return m_extrinsic_matrix; } - - const Box_cmex::ExtrinsicMatrix& get_extrinsic_matrix() const { return m_extrinsic_matrix; } - - - void add_region_item_id(heif_item_id id) { m_region_item_ids.push_back(id); } - - const std::vector& get_region_item_ids() const { return m_region_item_ids; } - - private: - HeifContext* m_heif_context; - - heif_item_id m_id = 0; - uint32_t m_width = 0, m_height = 0; // after all transformations have been applied - bool m_is_primary = false; - - bool m_is_thumbnail = false; - - std::vector> m_thumbnails; - - bool m_is_alpha_channel = false; - bool m_premultiplied_alpha = false; - std::shared_ptr m_alpha_channel; - - bool m_is_depth_channel = false; - std::shared_ptr m_depth_channel; - - bool m_has_depth_representation_info = false; - struct heif_depth_representation_info m_depth_representation_info; - - bool m_is_aux_image = false; - std::string m_aux_image_type; - std::vector> m_aux_images; - - std::vector> m_metadata; - - std::shared_ptr m_color_profile_nclx; - std::shared_ptr m_color_profile_icc; - - bool m_miaf_compatible = true; - - std::vector m_region_item_ids; + Error read(const std::shared_ptr& reader); - bool m_has_intrinsic_matrix = false; - Box_cmin::AbsoluteIntrinsicMatrix m_intrinsic_matrix{}; + Error read_from_file(const char* input_filename); - bool m_has_extrinsic_matrix = false; - Box_cmex::ExtrinsicMatrix m_extrinsic_matrix{}; - }; + Error read_from_memory(const void* data, size_t size, bool copy); - std::shared_ptr get_heif_file() { return m_heif_file; } + std::shared_ptr get_heif_file() const { return m_heif_file; } - std::vector> get_top_level_images() { return m_top_level_images; } - std::shared_ptr get_top_level_image(heif_item_id id) - { - for (auto& img : m_top_level_images) { - if (img->get_id() == id) { - return img; - } - } + // === image items === - return nullptr; - } + std::vector> get_top_level_images(bool return_error_images); - std::shared_ptr get_top_level_image(heif_item_id id) const - { - return const_cast(this)->get_top_level_image(id); + void insert_image_item(heif_item_id id, const std::shared_ptr& img) { + m_all_images.insert(std::make_pair(id, img)); } - std::shared_ptr get_image(heif_item_id id) - { - auto iter = m_all_images.find(id); - if (iter == m_all_images.end()) { - return nullptr; - } - else { - return iter->second; - } - } + std::shared_ptr get_image(heif_item_id id, bool return_error_images); - std::shared_ptr get_image(heif_item_id id) const + std::shared_ptr get_image(heif_item_id id, bool return_error_images) const { - return const_cast(this)->get_image(id); + return const_cast(this)->get_image(id, return_error_images); } - std::shared_ptr get_primary_image() { return m_primary_image; } + std::shared_ptr get_primary_image(bool return_error_image); bool is_image(heif_item_id ID) const; bool has_alpha(heif_item_id ID) const; - Error decode_image_user(heif_item_id ID, std::shared_ptr& img, - heif_colorspace out_colorspace, - heif_chroma out_chroma, - const struct heif_decoding_options& options) const; + Result> decode_image(heif_item_id ID, + heif_colorspace out_colorspace, + heif_chroma out_chroma, + const struct heif_decoding_options& options, + bool decode_only_tile, uint32_t tx, uint32_t ty) const; - Error decode_image_planar(heif_item_id ID, std::shared_ptr& img, - heif_colorspace out_colorspace, - const struct heif_decoding_options& options, - bool alphaImage) const; + Error get_id_of_non_virtual_child_image(heif_item_id in, heif_item_id& out) const; std::string debug_dump_boxes() const; // === writing === + void write(StreamWriter& writer); + // Create all boxes necessary for an empty HEIF file. // Note that this is no valid HEIF file, since some boxes (e.g. pitm) are generated, but // contain no valid data yet. @@ -407,85 +122,33 @@ class HeifContext : public ErrorBuffer struct heif_encoder* encoder, const struct heif_encoding_options& options, enum heif_image_input_class input_class, - std::shared_ptr& out_image); - - Error encode_grid(const std::vector>& tiles, - uint16_t rows, - uint16_t columns, - struct heif_encoder* encoder, - const struct heif_encoding_options& options, - std::shared_ptr& out_image); - - Error encode_image_as_hevc(const std::shared_ptr& image, - struct heif_encoder* encoder, - const struct heif_encoding_options& options, - enum heif_image_input_class input_class, - std::shared_ptr& out_image); - - Error encode_image_as_vvc(const std::shared_ptr& image, - struct heif_encoder* encoder, - const struct heif_encoding_options& options, - enum heif_image_input_class input_class, - std::shared_ptr& out_image); - - Error encode_image_as_av1(const std::shared_ptr& image, - struct heif_encoder* encoder, - const struct heif_encoding_options& options, - enum heif_image_input_class input_class, - std::shared_ptr& out_image); - - Error encode_image_as_jpeg(const std::shared_ptr& image, - struct heif_encoder* encoder, - const struct heif_encoding_options& options, - enum heif_image_input_class input_class, - std::shared_ptr& out_image); - - Error encode_image_as_jpeg2000(const std::shared_ptr& image, - struct heif_encoder* encoder, - const struct heif_encoding_options& options, - enum heif_image_input_class input_class, - std::shared_ptr& out_image); - - Error encode_image_as_uncompressed(const std::shared_ptr& src_image, - struct heif_encoder* encoder, - const struct heif_encoding_options& options, - enum heif_image_input_class input_class, - std::shared_ptr& out_image); - - Error encode_image_as_mask(const std::shared_ptr& src_image, - struct heif_encoder* encoder, - const struct heif_encoding_options& options, - enum heif_image_input_class input_class, - std::shared_ptr& out_image); - - // write PIXI, CLLI, MDVC - void write_image_metadata(std::shared_ptr src_image, int image_id); - - void set_primary_image(const std::shared_ptr& image); - - Error set_primary_item(heif_item_id id); + std::shared_ptr& out_image); + + void set_primary_image(const std::shared_ptr& image); bool is_primary_image_set() const { return m_primary_image != nullptr; } - Error assign_thumbnail(const std::shared_ptr& master_image, - const std::shared_ptr& thumbnail_image); + Error assign_thumbnail(const std::shared_ptr& master_image, + const std::shared_ptr& thumbnail_image); Error encode_thumbnail(const std::shared_ptr& image, struct heif_encoder* encoder, const struct heif_encoding_options& options, int bbox_size, - std::shared_ptr& out_image_handle); + std::shared_ptr& out_image_handle); - Error add_exif_metadata(const std::shared_ptr& master_image, const void* data, int size); + Error add_exif_metadata(const std::shared_ptr& master_image, const void* data, int size); - Error add_XMP_metadata(const std::shared_ptr& master_image, const void* data, int size, heif_metadata_compression compression); + Error add_XMP_metadata(const std::shared_ptr& master_image, const void* data, int size, heif_metadata_compression compression); - Error add_generic_metadata(const std::shared_ptr& master_image, const void* data, int size, - const char* item_type, const char* content_type, const char* item_uri_type, + Error add_generic_metadata(const std::shared_ptr& master_image, const void* data, int size, + uint32_t item_type, const char* content_type, const char* item_uri_type, heif_metadata_compression compression, heif_item_id* out_item_id); heif_property_id add_property(heif_item_id targetItem, std::shared_ptr property, bool essential); + Result add_pyramid_group(const std::vector& layers); + // --- region items void add_region_item(std::shared_ptr region_item) @@ -507,50 +170,26 @@ class HeifContext : public ErrorBuffer void add_region_referenced_mask_ref(heif_item_id region_item_id, heif_item_id mask_item_id); - void write(StreamWriter& writer); - private: - std::map> m_all_images; + std::map> m_all_images; // We store this in a vector because we need stable indices for the C API. // TODO: stable indices are obsolet now... - std::vector> m_top_level_images; + std::vector> m_top_level_images; - std::shared_ptr m_primary_image; // shortcut to primary image + std::shared_ptr m_primary_image; // shortcut to primary image std::shared_ptr m_heif_file; int m_max_decoding_threads = 4; - uint32_t m_maximum_image_width_limit; - uint32_t m_maximum_image_height_limit; + heif_security_limits m_limits; std::vector> m_region_items; Error interpret_heif_file(); - void remove_top_level_image(const std::shared_ptr& image); - - Error decode_full_grid_image(heif_item_id ID, - std::shared_ptr& img, - const std::vector& grid_data, - const heif_decoding_options& options) const; - - Error decode_and_paste_tile_image(heif_item_id tileID, - const std::shared_ptr& out_image, - int x0, int y0, - const heif_decoding_options& options) const; - - Error decode_derived_image(heif_item_id ID, - std::shared_ptr& img, - const heif_decoding_options& options) const; - - Error decode_overlay_image(heif_item_id ID, - std::shared_ptr& img, - const std::vector& overlay_data, - const heif_decoding_options& options) const; - - Error get_id_of_non_virtual_child_image(heif_item_id in, heif_item_id& out) const; + void remove_top_level_image(const std::shared_ptr& image); }; #endif diff --git a/libheif/error.cc b/libheif/error.cc index 2735c12981..52ee52bf62 100644 --- a/libheif/error.cc +++ b/libheif/error.cc @@ -106,6 +106,8 @@ const char* Error::get_error_string(heif_suberror_code err) return "No 'vvcC' box"; case heif_suberror_No_av1C_box: return "No 'av1C' box"; + case heif_suberror_No_avcC_box: + return "No 'avcC' box"; case heif_suberror_No_pitm_box: return "No 'pitm' box"; case heif_suberror_No_ipco_box: @@ -168,12 +170,18 @@ const char* Error::get_error_string(heif_suberror_code err) return "Camera extrinsic matrix undefined"; case heif_suberror_Invalid_J2K_codestream: return "Invalid JPEG 2000 codestream"; + case heif_suberror_Decompression_invalid_data: + return "Invalid data in generic compression inflation"; + case heif_suberror_No_icbr_box: + return "No 'icbr' box"; // --- Memory_allocation_error --- case heif_suberror_Security_limit_exceeded: return "Security limit exceeded"; + case heif_suberror_Compression_initialisation_error: + return "Compression initialisation method error"; // --- Usage_error --- @@ -210,6 +218,10 @@ const char* Error::get_error_string(heif_suberror_code err) return "Unsupported item construction method"; case heif_suberror_Unsupported_header_compression_method: return "Unsupported header compression method"; + case heif_suberror_Unsupported_generic_compression_method: + return "Unsupported generic compression method"; + case heif_suberror_Unsupported_essential_property: + return "Unsupported essential item property"; // --- Encoder_plugin_error -- diff --git a/libheif/error.h b/libheif/error.h index 9a68dd4f23..2a8249c626 100644 --- a/libheif/error.h +++ b/libheif/error.h @@ -33,9 +33,7 @@ #include #include "libheif/heif.h" - - -static constexpr char kSuccess[] = "Success"; +#include class ErrorBuffer @@ -107,8 +105,20 @@ inline std::ostream& operator<<(std::ostream& ostr, const Error& err) template class Result { public: + Result() = default; + + Result(const T& v) : value(v), error(Error::Ok) {} + + Result(const Error& e) : error(e) {} + operator bool() const { return error.error_code == heif_error_Ok; } + T& operator*() + { + assert(error.error_code == heif_error_Ok); + return value; + } + T value; Error error; }; diff --git a/libheif/file.cc b/libheif/file.cc index ea66d5279a..83c61ffcb0 100644 --- a/libheif/file.cc +++ b/libheif/file.cc @@ -22,10 +22,11 @@ #include "box.h" #include "libheif/heif.h" #include "libheif/heif_properties.h" -#include "metadata_compression.h" -#include "codecs/jpeg2000.h" -#include "codecs/jpeg.h" -#include "codecs/vvc.h" +#include "compression.h" +#include "image-items/jpeg2000.h" +#include "image-items/jpeg.h" +#include "image-items/vvc.h" +#include "codecs/uncompressed/unc_boxes.h" #include #include @@ -47,14 +48,17 @@ #if WITH_UNCOMPRESSED_CODEC -#include "codecs/uncompressed_image.h" +#include "image-items/unc_image.h" #endif // TODO: make this a decoder option #define STRICT_PARSING false -HeifFile::HeifFile() = default; +HeifFile::HeifFile() +{ + m_file_layout = std::make_shared(); +} HeifFile::~HeifFile() = default; @@ -120,19 +124,24 @@ Error HeifFile::read_from_memory(const void* data, size_t size, bool copy) Error HeifFile::read(const std::shared_ptr& reader) { + assert(m_limits); + m_input_stream = reader; - uint64_t maxSize = std::numeric_limits::max(); - BitstreamRange range(m_input_stream, maxSize); + Error err; + err = m_file_layout->read(reader, m_limits); + if (err) { + return err; + } - Error error = parse_heif_file(range); + Error error = parse_heif_file(); return error; } void HeifFile::new_empty_file() { - m_input_stream.reset(); + //m_input_stream.reset(); m_top_level_boxes.clear(); m_ftyp_box = std::make_shared(); @@ -267,10 +276,11 @@ std::string HeifFile::debug_dump_boxes() const } -Error HeifFile::parse_heif_file(BitstreamRange& range) +Error HeifFile::parse_heif_file() { // --- read all top-level boxes +#if 0 for (;;) { std::shared_ptr box; Error error = Box::read(range, &box); @@ -303,7 +313,14 @@ Error HeifFile::parse_heif_file(BitstreamRange& range) m_ftyp_box = std::dynamic_pointer_cast(box); } } +#endif + + m_ftyp_box = m_file_layout->get_ftyp_box(); + m_meta_box = m_file_layout->get_meta_box(); + m_top_level_boxes.push_back(m_ftyp_box); + m_top_level_boxes.push_back(m_meta_box); + // TODO: we are missing 'mdat' top level boxes // --- check whether this is a HEIF file and its structural format @@ -333,7 +350,7 @@ Error HeifFile::parse_heif_file(BitstreamRange& range) } - m_hdlr_box = std::dynamic_pointer_cast(m_meta_box->get_child_box(fourcc("hdlr"))); + m_hdlr_box = m_meta_box->get_child_box(); if (STRICT_PARSING && !m_hdlr_box) { return Error(heif_error_Invalid_input, heif_suberror_No_hdlr_box); @@ -348,25 +365,25 @@ Error HeifFile::parse_heif_file(BitstreamRange& range) // --- find mandatory boxes needed for image decoding - m_pitm_box = std::dynamic_pointer_cast(m_meta_box->get_child_box(fourcc("pitm"))); + m_pitm_box = m_meta_box->get_child_box(); if (!m_pitm_box) { return Error(heif_error_Invalid_input, heif_suberror_No_pitm_box); } - m_iprp_box = std::dynamic_pointer_cast(m_meta_box->get_child_box(fourcc("iprp"))); + m_iprp_box = m_meta_box->get_child_box(); if (!m_iprp_box) { return Error(heif_error_Invalid_input, heif_suberror_No_iprp_box); } - m_ipco_box = std::dynamic_pointer_cast(m_iprp_box->get_child_box(fourcc("ipco"))); + m_ipco_box = m_iprp_box->get_child_box(); if (!m_ipco_box) { return Error(heif_error_Invalid_input, heif_suberror_No_ipco_box); } - auto ipma_boxes = m_iprp_box->get_typed_child_boxes(fourcc("ipma")); + auto ipma_boxes = m_iprp_box->get_child_boxes(); if (ipma_boxes.empty()) { return Error(heif_error_Invalid_input, heif_suberror_No_ipma_box); @@ -376,15 +393,15 @@ Error HeifFile::parse_heif_file(BitstreamRange& range) } m_ipma_box = ipma_boxes[0]; - m_iloc_box = std::dynamic_pointer_cast(m_meta_box->get_child_box(fourcc("iloc"))); + m_iloc_box = m_meta_box->get_child_box(); if (!m_iloc_box) { return Error(heif_error_Invalid_input, heif_suberror_No_iloc_box); } - m_idat_box = std::dynamic_pointer_cast(m_meta_box->get_child_box(fourcc("idat"))); + m_idat_box = m_meta_box->get_child_box(); - m_iref_box = std::dynamic_pointer_cast(m_meta_box->get_child_box(fourcc("iref"))); + m_iref_box = m_meta_box->get_child_box(); if (m_iref_box) { Error error = check_for_ref_cycle(get_primary_image_ID(), m_iref_box); if (error) { @@ -392,20 +409,20 @@ Error HeifFile::parse_heif_file(BitstreamRange& range) } } - m_iinf_box = std::dynamic_pointer_cast(m_meta_box->get_child_box(fourcc("iinf"))); + m_iinf_box = m_meta_box->get_child_box(); if (!m_iinf_box) { return Error(heif_error_Invalid_input, heif_suberror_No_iinf_box); } + m_grpl_box = m_meta_box->get_child_box(); // --- build list of images - std::vector> infe_boxes = m_iinf_box->get_child_boxes(fourcc("infe")); + std::vector> infe_boxes = m_iinf_box->get_child_boxes(); - for (auto& box : infe_boxes) { - std::shared_ptr infe_box = std::dynamic_pointer_cast(box); + for (auto& infe_box : infe_boxes) { if (!infe_box) { return Error(heif_error_Invalid_input, heif_suberror_No_infe_box); @@ -463,14 +480,14 @@ bool HeifFile::has_item_with_id(heif_item_id ID) const } -std::string HeifFile::get_item_type(heif_item_id ID) const +uint32_t HeifFile::get_item_type_4cc(heif_item_id ID) const { auto infe_box = get_infe_box(ID); if (!infe_box) { - return ""; + return 0; } - return infe_box->get_item_type(); + return infe_box->get_item_type_4cc(); } @@ -511,259 +528,102 @@ Error HeifFile::get_properties(heif_item_id imageID, } -heif_chroma HeifFile::get_image_chroma_from_configuration(heif_item_id imageID) const +Error HeifFile::get_uncompressed_item_data(heif_item_id ID, std::vector* data) const { - // HEVC + assert(m_limits); - auto box = m_ipco_box->get_property_for_item_ID(imageID, m_ipma_box, fourcc("hvcC")); - std::shared_ptr hvcC_box = std::dynamic_pointer_cast(box); - if (hvcC_box) { - return (heif_chroma) (hvcC_box->get_configuration().chroma_format); - } - - - // VVC - - box = m_ipco_box->get_property_for_item_ID(imageID, m_ipma_box, fourcc("vvcC")); - std::shared_ptr vvcC_box = std::dynamic_pointer_cast(box); - if (vvcC_box) { - return (heif_chroma) (vvcC_box->get_configuration().chroma_format_idc); - } - - - // AV1 +#if ENABLE_PARALLEL_TILE_DECODING + // std::lock_guard guard(m_read_mutex); // TODO: I think that this is not needed anymore because this function is not used for image data anymore. +#endif - box = m_ipco_box->get_property_for_item_ID(imageID, m_ipma_box, fourcc("av1C")); - std::shared_ptr av1C_box = std::dynamic_pointer_cast(box); - if (av1C_box) { - Box_av1C::configuration config = av1C_box->get_configuration(); - if (config.chroma_subsampling_x == 1 && - config.chroma_subsampling_y == 1) { - return heif_chroma_420; - } - else if (config.chroma_subsampling_x == 1 && - config.chroma_subsampling_y == 0) { - return heif_chroma_422; - } - else if (config.chroma_subsampling_x == 0 && - config.chroma_subsampling_y == 0) { - return heif_chroma_444; - } - else { - return heif_chroma_undefined; - } + if (!image_exists(ID)) { + return Error(heif_error_Usage_error, + heif_suberror_Nonexisting_item_referenced); } - - assert(false); - return heif_chroma_undefined; -} - - -int HeifFile::get_luma_bits_per_pixel_from_configuration(heif_item_id imageID) const -{ - std::string image_type = get_item_type(imageID); - - // HEVC - - if (image_type == "hvc1") { - auto box = m_ipco_box->get_property_for_item_ID(imageID, m_ipma_box, fourcc("hvcC")); - std::shared_ptr hvcC_box = std::dynamic_pointer_cast(box); - if (hvcC_box) { - return hvcC_box->get_configuration().bit_depth_luma; - } + auto infe_box = get_infe_box(ID); + if (!infe_box) { + return Error(heif_error_Usage_error, + heif_suberror_Nonexisting_item_referenced); } - // VVC - - if (image_type == "vvc1") { - auto box = m_ipco_box->get_property_for_item_ID(imageID, m_ipma_box, fourcc("vvcC")); - std::shared_ptr vvcC_box = std::dynamic_pointer_cast(box); - if (vvcC_box) { - Box_vvcC::configuration config = vvcC_box->get_configuration(); - if (config.bit_depth_present_flag) { - return config.bit_depth; - } - else { - return 8; // TODO: what shall we do if the bit-depth is unknown? Use PIXI? - } - } - } + uint32_t item_type = infe_box->get_item_type_4cc(); + std::string content_type = infe_box->get_content_type(); - - // AV1 + // --- decompress data - if (image_type == "av01") { - auto box = m_ipco_box->get_property_for_item_ID(imageID, m_ipma_box, fourcc("av1C")); - std::shared_ptr av1C_box = std::dynamic_pointer_cast(box); - if (av1C_box) { - Box_av1C::configuration config = av1C_box->get_configuration(); - if (!config.high_bitdepth) { - return 8; - } - else if (config.twelve_bit) { - return 12; + Error error; + bool read_uncompressed = true; + if (item_type == fourcc("mime")) { + std::string encoding = infe_box->get_content_encoding(); + if (encoding == "compress_zlib") { +#if HAVE_ZLIB + read_uncompressed = false; + std::vector compressed_data; + error = m_iloc_box->read_data(ID, m_input_stream, m_idat_box, &compressed_data, m_limits); + if (error) { + return error; } - else { - return 10; + error = decompress_zlib(compressed_data, data); + if (error) { + return error; } - } - } - - - // JPEG - - if (image_type == "jpeg" || (image_type=="mime" && get_content_type(imageID)=="image/jpeg")) { - return jpeg_get_bits_per_pixel(imageID); - } - - // JPEG 2000 - - if (image_type == "j2k1") { - JPEG2000MainHeader header; - Error err = header.parseHeader(*this, imageID); - if (err) { - return -1; - } - return header.get_precision(0); - } - -#if WITH_UNCOMPRESSED_CODEC - // Uncompressed - - if (image_type == "unci") { - int bpp = UncompressedImageCodec::get_luma_bits_per_pixel_from_configuration_unci(*this, imageID); - return bpp; - } +#else + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_header_compression_method, + encoding); #endif - - return -1; -} - - -int HeifFile::get_chroma_bits_per_pixel_from_configuration(heif_item_id imageID) const -{ - std::string image_type = get_item_type(imageID); - - // HEVC - - if (image_type == "hvc1") { - auto box = m_ipco_box->get_property_for_item_ID(imageID, m_ipma_box, fourcc("hvcC")); - std::shared_ptr hvcC_box = std::dynamic_pointer_cast(box); - if (hvcC_box) { - return hvcC_box->get_configuration().bit_depth_chroma; } - } - - // VVC - - if (image_type == "vvc1") { - auto box = m_ipco_box->get_property_for_item_ID(imageID, m_ipma_box, fourcc("vvcC")); - std::shared_ptr vvcC_box = std::dynamic_pointer_cast(box); - if (vvcC_box) { - Box_vvcC::configuration config = vvcC_box->get_configuration(); - if (config.bit_depth_present_flag) { - return config.bit_depth; + else if (encoding == "deflate") { +#if HAVE_ZLIB + read_uncompressed = false; + std::vector compressed_data; + error = m_iloc_box->read_data(ID, m_input_stream, m_idat_box, &compressed_data, m_limits); + if (error) { + return error; } - else { - return 8; // TODO: what shall we do if the bit-depth is unknown? Use PIXI? + error = decompress_deflate(compressed_data, data); + if (error) { + return error; } +#else + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_header_compression_method, + encoding); +#endif } - } - - // AV1 - - if (image_type == "av01") { - auto box = m_ipco_box->get_property_for_item_ID(imageID, m_ipma_box, fourcc("av1C")); - std::shared_ptr av1C_box = std::dynamic_pointer_cast(box); - if (av1C_box) { - Box_av1C::configuration config = av1C_box->get_configuration(); - if (!config.high_bitdepth) { - return 8; + else if (encoding == "br") { +#if HAVE_BROTLI + read_uncompressed = false; + std::vector compressed_data; + error = m_iloc_box->read_data(ID, m_input_stream, m_idat_box, &compressed_data, m_limits); + if (error) { + return error; } - else if (config.twelve_bit) { - return 12; - } - else { - return 10; + error = decompress_brotli(compressed_data, data); + if (error) { + return error; } +#else + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_header_compression_method, + encoding); +#endif } } - // JPEG - - if (image_type == "jpeg" || (image_type=="mime" && get_content_type(imageID)=="image/jpeg")) { - return jpeg_get_bits_per_pixel(imageID); - } - - // JPEG 2000 - - if (image_type == "j2k1") { - JPEG2000MainHeader header; - Error err = header.parseHeader(*this, imageID); - if (err) { - return -1; - } - return header.get_precision(1); + if (read_uncompressed) { + return m_iloc_box->read_data(ID, m_input_stream, m_idat_box, data, m_limits); } - return -1; + return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_codec); } -// This checks whether a start code FFCx with nibble 'x' is a SOF marker. -// E.g. FFC0-FFC3 are, while FFC4 is not. -static bool isSOF[16] = { 1,1,1,1,0,1,1,1,0,1,1,1,0,1,1,1 }; - -int HeifFile::jpeg_get_bits_per_pixel(heif_item_id imageID) const +Error HeifFile::append_data_from_iloc(heif_item_id ID, std::vector& out_data, uint64_t offset, uint64_t size) const { - std::vector data; - Error err = get_compressed_image_data(imageID, &data); - if (err) { - return -1; - } - - for (size_t i = 0; i + 1 < data.size(); i++) { - if (data[i] == 0xFF && (data[i+1] & 0xF0) == 0xC0 && isSOF[data[i+1] & 0x0F]) { - i += 4; - if (i < data.size()) { - return data[i]; - } - else { - return -1; - } - } - } - - return -1; -} - - -Error HeifFile::get_compressed_image_data(heif_item_id ID, std::vector* data) const -{ -#if ENABLE_PARALLEL_TILE_DECODING - std::lock_guard guard(m_read_mutex); -#endif - - if (!image_exists(ID)) { - return Error(heif_error_Usage_error, - heif_suberror_Nonexisting_item_referenced); - } - - auto infe_box = get_infe_box(ID); - if (!infe_box) { - return Error(heif_error_Usage_error, - heif_suberror_Nonexisting_item_referenced); - } - - - std::string item_type = infe_box->get_item_type(); - std::string content_type = infe_box->get_content_type(); - - // --- get coded image data pointers - - auto items = m_iloc_box->get_items(); + const auto& items = m_iloc_box->get_items(); const Box_iloc::Item* item = nullptr; for (const auto& i : items) { if (i.item_ID == ID) { @@ -775,216 +635,12 @@ Error HeifFile::get_compressed_image_data(heif_item_id ID, std::vector* std::stringstream sstr; sstr << "Item with ID " << ID << " has no compressed data"; - return Error(heif_error_Invalid_input, - heif_suberror_No_item_data, - sstr.str()); - } - - Error error = Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_codec); - if (item_type == "hvc1") { - // --- --- --- HEVC - - // --- get properties for this image - - std::vector> properties; - Error err = m_ipco_box->get_properties_for_item_ID(ID, m_ipma_box, properties); - if (err) { - return err; - } - - // --- get codec configuration - - std::shared_ptr hvcC_box; - for (auto& prop : properties) { - if (prop->get_short_type() == fourcc("hvcC")) { - hvcC_box = std::dynamic_pointer_cast(prop); - if (hvcC_box) { - break; - } - } - } - - if (!hvcC_box) { - // Should always have an hvcC box, because we are checking this in - // heif_context::interpret_heif_file() - assert(false); - return Error(heif_error_Invalid_input, - heif_suberror_No_hvcC_box); - } - else if (!hvcC_box->get_headers(data)) { - return Error(heif_error_Invalid_input, - heif_suberror_No_item_data); - } - - error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); - } - else if (item_type == "vvc1") { - // --- --- --- VVC - - // --- get properties for this image - - std::vector> properties; - Error err = m_ipco_box->get_properties_for_item_ID(ID, m_ipma_box, properties); - if (err) { - return err; - } - - // --- get codec configuration - - std::shared_ptr vvcC_box; - for (auto& prop : properties) { - if (prop->get_short_type() == fourcc("vvcC")) { - vvcC_box = std::dynamic_pointer_cast(prop); - if (vvcC_box) { - break; - } - } - } - - if (!vvcC_box) { - // Should always have an vvcC box, because we are checking this in - // heif_context::interpret_heif_file() - assert(false); - return Error(heif_error_Invalid_input, - heif_suberror_No_vvcC_box); - } - else if (!vvcC_box->get_headers(data)) { - return Error(heif_error_Invalid_input, - heif_suberror_No_item_data); - } - - error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); - } - else if (item_type == "av01") { - // --- --- --- AV1 - - // --- get properties for this image - - std::vector> properties; - Error err = m_ipco_box->get_properties_for_item_ID(ID, m_ipma_box, properties); - if (err) { - return err; - } - - // --- get codec configuration - - std::shared_ptr av1C_box; - for (auto& prop : properties) { - if (prop->get_short_type() == fourcc("av1C")) { - av1C_box = std::dynamic_pointer_cast(prop); - if (av1C_box) { - break; - } - } - } - - if (!av1C_box) { - // Should always have an hvcC box, because we are checking this in - // heif_context::interpret_heif_file() - return Error(heif_error_Invalid_input, - heif_suberror_No_av1C_box); - } - else if (!av1C_box->get_headers(data)) { - return Error(heif_error_Invalid_input, - heif_suberror_No_item_data); - } - - error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); - } - else if (item_type == "jpeg" || - (item_type == "mime" && get_content_type(ID) == "image/jpeg")) { - - // --- check if 'jpgC' is present - - std::vector> properties; - Error err = m_ipco_box->get_properties_for_item_ID(ID, m_ipma_box, properties); - if (err) { - return err; - } - - // --- get codec configuration - - std::shared_ptr jpgC_box; - for (auto& prop : properties) { - if (prop->get_short_type() == fourcc("jpgC")) { - jpgC_box = std::dynamic_pointer_cast(prop); - if (jpgC_box) { - *data = jpgC_box->get_data(); - break; - } - } - } - - error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); - } - else if (item_type == "j2k1") { - std::vector> properties; - Error err = m_ipco_box->get_properties_for_item_ID(ID, m_ipma_box, properties); - if (err) { - return err; - } - - // --- get codec configuration - - std::shared_ptr j2kH_box; - for (auto& prop : properties) { - if (prop->get_short_type() == fourcc("j2kH")) { - j2kH_box = std::dynamic_pointer_cast(prop); - if (j2kH_box) { - break; - } - } - } - - if (!j2kH_box) { - // Should always have an j2kH box, because we are checking this in - // heif_context::interpret_heif_file() - - //TODO - Correctly Find the j2kH box - // return Error(heif_error_Invalid_input, - // heif_suberror_Unspecified); - } - // else if (!j2kH_box->get_headers(data)) { - // return Error(heif_error_Invalid_input, - // heif_suberror_No_item_data); - // } - - error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); - } - else if (true || // fallback case for all kinds of generic metadata (e.g. 'iptc') - item_type == "grid" || - item_type == "iovl" || - item_type == "Exif" || - (item_type == "mime" && content_type == "application/rdf+xml")) { - - bool read_uncompressed = true; - if (item_type == "mime") { - std::string encoding = infe_box->get_content_encoding(); - if (encoding == "deflate") { -#if WITH_DEFLATE_HEADER_COMPRESSION - read_uncompressed = false; - std::vector compressed_data; - error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, &compressed_data); - *data = inflate(compressed_data); -#else - return Error(heif_error_Unsupported_feature, - heif_suberror_Unsupported_header_compression_method, - encoding); -#endif - } - } - - if (read_uncompressed) { - error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); - } - } - - if (error != Error::Ok) { - return error; + return {heif_error_Invalid_input, + heif_suberror_No_item_data, + sstr.str()}; } - return Error::Ok; + return m_iloc_box->read_data(ID, m_input_stream, m_idat_box, &out_data, offset, size, m_limits); } @@ -992,42 +648,25 @@ Error HeifFile::get_item_data(heif_item_id ID, std::vector* out_data, h { Error error; + assert(m_limits); + auto infe_box = get_infe_box(ID); if (!infe_box) { return {heif_error_Usage_error, heif_suberror_Nonexisting_item_referenced}; } - std::string item_type = infe_box->get_item_type(); + uint32_t item_type = infe_box->get_item_type_4cc(); std::string content_type = infe_box->get_content_type(); - // --- get item - - auto items = m_iloc_box->get_items(); - const Box_iloc::Item* item = nullptr; - for (const auto& i : items) { - if (i.item_ID == ID) { - item = &i; - break; - } - } - if (!item) { - std::stringstream sstr; - sstr << "Item with ID " << ID << " has no data"; - - return {heif_error_Invalid_input, - heif_suberror_No_item_data, - sstr.str()}; - } - // --- non 'mime' data (uncompressed) - if (item_type != "mime") { + if (item_type != fourcc("mime")) { if (out_compression) { *out_compression = heif_metadata_compression_off; } - return m_iloc_box->read_data(*item, m_input_stream, m_idat_box, out_data); + return m_iloc_box->read_data(ID, m_input_stream, m_idat_box, out_data, m_limits); } @@ -1044,11 +683,17 @@ Error HeifFile::get_item_data(heif_item_id ID, std::vector* out_data, h *out_compression = heif_metadata_compression_off; } - return m_iloc_box->read_data(*item, m_input_stream, m_idat_box, out_data); + return m_iloc_box->read_data(ID, m_input_stream, m_idat_box, out_data, m_limits); + } + else if (encoding == "compress_zlib") { + compression = heif_metadata_compression_zlib; } else if (encoding == "deflate") { compression = heif_metadata_compression_deflate; } + else if (encoding == "br") { + compression = heif_metadata_compression_brotli; + } else { compression = heif_metadata_compression_unknown; } @@ -1056,7 +701,7 @@ Error HeifFile::get_item_data(heif_item_id ID, std::vector* out_data, h // read compressed data std::vector compressed_data; - error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, &compressed_data); + error = m_iloc_box->read_data(ID, m_input_stream, m_idat_box, &compressed_data, m_limits); if (error) { return error; } @@ -1073,10 +718,15 @@ Error HeifFile::get_item_data(heif_item_id ID, std::vector* out_data, h // decompress the data switch (compression) { -#if WITH_DEFLATE_HEADER_COMPRESSION +#if HAVE_ZLIB + case heif_metadata_compression_zlib: + return decompress_zlib(compressed_data, out_data); case heif_metadata_compression_deflate: - *out_data = inflate(compressed_data); - return Error::Ok; + return decompress_deflate(compressed_data, out_data); +#endif +#if HAVE_BROTLI + case heif_metadata_compression_brotli: + return decompress_brotli(compressed_data, out_data); #endif default: return {heif_error_Unsupported_filetype, heif_suberror_Unsupported_header_compression_method}; @@ -1084,53 +734,38 @@ Error HeifFile::get_item_data(heif_item_id ID, std::vector* out_data, h } +// TODO: we should use a acquire() / release() approach here so that we can get multiple IDs before actually creating infe boxes heif_item_id HeifFile::get_unused_item_id() const { - for (heif_item_id id = 1;; - id++) { + heif_item_id max_id = 0; - bool id_exists = false; + // TODO: replace with better algorithm and data-structure - for (const auto& infe : m_infe_boxes) { - if (infe.second->get_item_ID() == id) { - id_exists = true; - break; - } - } - - if (!id_exists) { - return id; - } + for (const auto& infe : m_infe_boxes) { + max_id = std::max(max_id, infe.second->get_item_ID()); } - assert(false); // should never be reached - return 0; -} - + assert(max_id != 0xFFFFFFFF); -heif_item_id HeifFile::add_new_image(const char* item_type) -{ - auto box = add_new_infe_box(item_type); - return box->get_item_ID(); + return max_id + 1; } -heif_item_id HeifFile::add_new_hidden_image(const char* item_type) +heif_item_id HeifFile::add_new_image(uint32_t item_type) { auto box = add_new_infe_box(item_type); - box->set_hidden_item(true); return box->get_item_ID(); } -std::shared_ptr HeifFile::add_new_infe_box(const char* item_type) +std::shared_ptr HeifFile::add_new_infe_box(uint32_t item_type) { heif_item_id id = get_unused_item_id(); auto infe = std::make_shared(); infe->set_item_ID(id); infe->set_hidden_item(false); - infe->set_item_type(item_type); + infe->set_item_type_4cc(item_type); m_infe_boxes[id] = infe; m_iinf_box->append_child_box(infe); @@ -1139,31 +774,31 @@ std::shared_ptr HeifFile::add_new_infe_box(const char* item_type) } -void HeifFile::add_ispe_property(heif_item_id id, uint32_t width, uint32_t height) +void HeifFile::add_ispe_property(heif_item_id id, uint32_t width, uint32_t height, bool essential) { auto ispe = std::make_shared(); ispe->set_size(width, height); int index = m_ipco_box->find_or_append_child_box(ispe); - m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{false, uint16_t(index + 1)}); + m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{essential, uint16_t(index + 1)}); } -void HeifFile::add_clap_property(heif_item_id id, uint32_t clap_width, uint32_t clap_height, - uint32_t image_width, uint32_t image_height) + + +heif_property_id HeifFile::add_property(heif_item_id id, const std::shared_ptr& property, bool essential) { - auto clap = std::make_shared(); - clap->set(clap_width, clap_height, image_width, image_height); + int index = m_ipco_box->find_or_append_child_box(property); - int index = m_ipco_box->find_or_append_child_box(clap); + m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{essential, uint16_t(index + 1)}); - m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{true, uint16_t(index + 1)}); + return index + 1; } -heif_property_id HeifFile::add_property(heif_item_id id, const std::shared_ptr& property, bool essential) +heif_property_id HeifFile::add_property_without_deduplication(heif_item_id id, const std::shared_ptr& property, bool essential) { - int index = m_ipco_box->find_or_append_child_box(property); + int index = m_ipco_box->append_child_box(property); m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{essential, uint16_t(index + 1)}); @@ -1233,172 +868,7 @@ void HeifFile::add_orientation_properties(heif_item_id id, heif_orientation orie } -void HeifFile::add_pixi_property(heif_item_id id, uint8_t c1, uint8_t c2, uint8_t c3) -{ - auto pixi = std::make_shared(); - pixi->add_channel_bits(c1); - if (c2 || c3) { - pixi->add_channel_bits(c2); - pixi->add_channel_bits(c3); - } - - int index = m_ipco_box->find_or_append_child_box(pixi); - - m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{false, uint16_t(index + 1)}); -} - - -void HeifFile::add_vvcC_property(heif_item_id id) -{ - auto vvcC = std::make_shared(); - int index = m_ipco_box->append_child_box(vvcC); - - m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{true, uint16_t(index + 1)}); -} - - -Error HeifFile::append_vvcC_nal_data(heif_item_id id, const std::vector& nal_data) -{ - auto vvcC = std::dynamic_pointer_cast(m_ipco_box->get_property_for_item_ID(id, - m_ipma_box, - fourcc("vvcC"))); - - if (vvcC) { - vvcC->append_nal_data(nal_data); - return Error::Ok; - } - else { - // Should always have an vvcC box, because we are checking this in - // heif_context::interpret_heif_file() - assert(false); - return Error(heif_error_Usage_error, - heif_suberror_No_vvcC_box); - } -} - - -Error HeifFile::set_vvcC_configuration(heif_item_id id, const Box_vvcC::configuration& config) -{ - auto vvcC = std::dynamic_pointer_cast(m_ipco_box->get_property_for_item_ID(id, - m_ipma_box, - fourcc("vvcC"))); - - if (vvcC) { - vvcC->set_configuration(config); - return Error::Ok; - } - else { - return Error(heif_error_Usage_error, - heif_suberror_No_vvcC_box); - } -} - - -Error HeifFile::append_vvcC_nal_data(heif_item_id id, const uint8_t* data, size_t size) -{ - std::vector> properties; - - auto vvcC = std::dynamic_pointer_cast(m_ipco_box->get_property_for_item_ID(id, - m_ipma_box, - fourcc("vvcC"))); - - if (vvcC) { - vvcC->append_nal_data(data, size); - return Error::Ok; - } - else { - return Error(heif_error_Usage_error, - heif_suberror_No_vvcC_box); - } -} - - -void HeifFile::add_hvcC_property(heif_item_id id) -{ - auto hvcC = std::make_shared(); - int index = m_ipco_box->append_child_box(hvcC); - - m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{true, uint16_t(index + 1)}); -} - - -Error HeifFile::append_hvcC_nal_data(heif_item_id id, const std::vector& nal_data) -{ - auto hvcC = std::dynamic_pointer_cast(m_ipco_box->get_property_for_item_ID(id, - m_ipma_box, - fourcc("hvcC"))); - - if (hvcC) { - hvcC->append_nal_data(nal_data); - return Error::Ok; - } - else { - // Should always have an hvcC box, because we are checking this in - // heif_context::interpret_heif_file() - assert(false); - return Error(heif_error_Usage_error, - heif_suberror_No_hvcC_box); - } -} - - -Error HeifFile::set_hvcC_configuration(heif_item_id id, const Box_hvcC::configuration& config) -{ - auto hvcC = std::dynamic_pointer_cast(m_ipco_box->get_property_for_item_ID(id, - m_ipma_box, - fourcc("hvcC"))); - - if (hvcC) { - hvcC->set_configuration(config); - return Error::Ok; - } - else { - return Error(heif_error_Usage_error, - heif_suberror_No_hvcC_box); - } -} - - -Error HeifFile::append_hvcC_nal_data(heif_item_id id, const uint8_t* data, size_t size) -{ - std::vector> properties; - - auto hvcC = std::dynamic_pointer_cast(m_ipco_box->get_property_for_item_ID(id, - m_ipma_box, - fourcc("hvcC"))); - - if (hvcC) { - hvcC->append_nal_data(data, size); - return Error::Ok; - } - else { - return Error(heif_error_Usage_error, - heif_suberror_No_hvcC_box); - } -} - - -void HeifFile::add_av1C_property(heif_item_id id, const Box_av1C::configuration& config) -{ - auto av1C = std::make_shared(); - av1C->set_configuration(config); - - add_property(id, av1C, true); -} - - -std::shared_ptr HeifFile::add_j2kH_property(heif_item_id id) -{ - auto j2kH = std::make_shared(); - int index = m_ipco_box->append_child_box(j2kH); // do not deduplicate because this can have a child box - - m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{true, uint16_t(index + 1)}); - - return j2kH; -} - - -Result HeifFile::add_infe(const char* item_type, const uint8_t* data, size_t size) +Result HeifFile::add_infe(uint32_t item_type, const uint8_t* data, size_t size) { Result result; @@ -1422,7 +892,7 @@ Result HeifFile::add_infe_mime(const char* content_type, heif_meta // create an infe box describing what kind of data we are storing (this also creates a new ID) - auto infe_box = add_new_infe_box("mime"); + auto infe_box = add_new_infe_box(fourcc("mime")); infe_box->set_hidden_item(true); infe_box->set_content_type(content_type); @@ -1441,7 +911,7 @@ Result HeifFile::add_precompressed_infe_mime(const char* content_t // create an infe box describing what kind of data we are storing (this also creates a new ID) - auto infe_box = add_new_infe_box("mime"); + auto infe_box = add_new_infe_box(fourcc("mime")); infe_box->set_hidden_item(true); infe_box->set_content_type(content_type); @@ -1460,7 +930,7 @@ Result HeifFile::add_infe_uri(const char* item_uri_type, const uin // create an infe box describing what kind of data we are storing (this also creates a new ID) - auto infe_box = add_new_infe_box("uri "); + auto infe_box = add_new_infe_box(fourcc("uri ")); infe_box->set_hidden_item(true); infe_box->set_item_uri_type(item_uri_type); @@ -1483,21 +953,31 @@ Error HeifFile::set_item_data(const std::shared_ptr& item, const uint8 // only set metadata compression for MIME type data which has 'content_encoding' field if (compression != heif_metadata_compression_off && - item->get_item_type() != "mime") { + item->get_item_type_4cc() != fourcc("mime")) { // TODO: error, compression not supported } std::vector data_array; - if (compression == heif_metadata_compression_deflate) { -#if WITH_DEFLATE_HEADER_COMPRESSION - data_array = deflate((const uint8_t*) data, size); - item->set_content_encoding("deflate"); + if (compression == heif_metadata_compression_zlib) { +#if HAVE_ZLIB + data_array = compress_zlib((const uint8_t*) data, size); + item->set_content_encoding("compress_zlib"); #else return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_header_compression_method); #endif } + else if (compression == heif_metadata_compression_deflate) { +#if HAVE_ZLIB + data_array = compress_deflate((const uint8_t*) data, size); + item->set_content_encoding("compress_zlib"); +#else + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_header_compression_method); +#endif + } + // TODO: brotli else { // uncompressed data, plain copy @@ -1507,7 +987,7 @@ Error HeifFile::set_item_data(const std::shared_ptr& item, const uint8 // copy the data into the file, store the pointer to it in an iloc box entry - append_iloc_data(item->get_item_ID(), data_array); + append_iloc_data(item->get_item_ID(), data_array, 0); return Error::Ok; } @@ -1517,7 +997,7 @@ Error HeifFile::set_precompressed_item_data(const std::shared_ptr& ite { // only set metadata compression for MIME type data which has 'content_encoding' field if (!content_encoding.empty() && - item->get_item_type() != "mime") { + item->get_item_type_4cc() != fourcc("mime")) { // TODO: error, compression not supported } @@ -1530,7 +1010,7 @@ Error HeifFile::set_precompressed_item_data(const std::shared_ptr& ite // copy the data into the file, store the pointer to it in an iloc box entry - append_iloc_data(item->get_item_ID(), data_array); + append_iloc_data(item->get_item_ID(), data_array, 0); return Error::Ok; } @@ -1542,21 +1022,12 @@ void HeifFile::append_iloc_data(heif_item_id id, const std::vector& nal } -void HeifFile::append_iloc_data_with_4byte_size(heif_item_id id, const uint8_t* data, size_t size) +void HeifFile::replace_iloc_data(heif_item_id id, uint64_t offset, const std::vector& data, uint8_t construction_method) { - std::vector nal; - nal.resize(size + 4); - - nal[0] = (uint8_t) ((size >> 24) & 0xFF); - nal[1] = (uint8_t) ((size >> 16) & 0xFF); - nal[2] = (uint8_t) ((size >> 8) & 0xFF); - nal[3] = (uint8_t) ((size >> 0) & 0xFF); - - memcpy(nal.data() + 4, data, size); - - append_iloc_data(id, nal); + m_iloc_box->replace_data(id, offset, data, construction_method); } + void HeifFile::set_primary_item_id(heif_item_id id) { m_pitm_box->set_item_ID(id); @@ -1573,32 +1044,53 @@ void HeifFile::add_iref_reference(heif_item_id from, uint32_t type, m_iref_box->add_references(from, type, to); } -void HeifFile::set_auxC_property(heif_item_id id, const std::string& type) + +void HeifFile::set_iref_reference(heif_item_id from, uint32_t type, int reference_idx, heif_item_id to_item) { - auto auxC = std::make_shared(); - auxC->set_aux_type(type); + assert(m_iref_box); + m_iref_box->overwrite_reference(from, type, reference_idx, to_item); +} - int index = m_ipco_box->find_or_append_child_box(auxC); - m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{true, uint16_t(index + 1)}); +void HeifFile::add_entity_group_box(const std::shared_ptr& entity_group_box) +{ + if (!m_grpl_box) { + m_grpl_box = std::make_shared(); + m_meta_box->append_child_box(m_grpl_box); + } + + m_grpl_box->append_child_box(entity_group_box); } -void HeifFile::set_color_profile(heif_item_id id, const std::shared_ptr& profile) + +std::shared_ptr HeifFile::get_entity_group(heif_entity_group_id id) { - auto colr = std::make_shared(); - colr->set_color_profile(profile); + if (!m_grpl_box) { + return nullptr; + } + + const auto& entityGroups = m_grpl_box->get_all_child_boxes(); + for (auto& groupBase : entityGroups) { + auto group = std::dynamic_pointer_cast(groupBase); + assert(group); - int index = m_ipco_box->find_or_append_child_box(colr); - m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{false, uint16_t(index + 1)}); + if (group->get_group_id() == id) { + return group; + } + } + + return nullptr; } -// TODO: the hdlr box is probably not the right place for this. Into which box should we write comments? -void HeifFile::set_hdlr_library_info(const std::string& encoder_plugin_version) +void HeifFile::set_auxC_property(heif_item_id id, const std::string& type) { - std::stringstream sstr; - sstr << "libheif (" << LIBHEIF_VERSION << ") / " << encoder_plugin_version; - m_hdlr_box->set_name(sstr.str()); + auto auxC = std::make_shared(); + auxC->set_aux_type(type); + + int index = m_ipco_box->find_or_append_child_box(auxC); + + m_ipma_box->add_property_for_item_ID(id, Box_ipma::PropertyAssociation{true, uint16_t(index + 1)}); } #if defined(__MINGW32__) || defined(__MINGW64__) || defined(_MSC_VER) diff --git a/libheif/file.h b/libheif/file.h index a489af9a87..2a3eb2024a 100644 --- a/libheif/file.h +++ b/libheif/file.h @@ -23,9 +23,11 @@ #include "box.h" #include "nclx.h" -#include "codecs/avif.h" -#include "codecs/hevc.h" -#include "codecs/vvc.h" +#include "image-items/avif.h" +#include "image-items/hevc.h" +#include "image-items/vvc.h" +#include "codecs/uncompressed/unc_boxes.h" +#include "file_layout.h" #include #include @@ -33,6 +35,7 @@ #include #include #include +#include #if ENABLE_PARALLEL_TILE_DECODING @@ -43,10 +46,9 @@ class HeifPixelImage; -class HeifImage; - class Box_j2kH; + class HeifFile { public: @@ -54,12 +56,18 @@ class HeifFile ~HeifFile(); + // The limits will be shared from the HeifContext limits. + // You have to make sure that the pointer points to a valid object as long as the HeifFile is used. + void set_security_limits(const heif_security_limits* limits) { m_limits = limits; } + Error read(const std::shared_ptr& reader); Error read_from_file(const char* input_filename); Error read_from_memory(const void* data, size_t size, bool copy); + std::shared_ptr get_reader() { return m_input_stream; } + void new_empty_file(); void set_brand(heif_compression_format format, bool miaf_compatible); @@ -78,15 +86,21 @@ class HeifFile bool has_item_with_id(heif_item_id ID) const; - std::string get_item_type(heif_item_id ID) const; + uint32_t get_item_type_4cc(heif_item_id ID) const; std::string get_content_type(heif_item_id ID) const; std::string get_item_uri_type(heif_item_id ID) const; - Error get_compressed_image_data(heif_item_id ID, std::vector* out_data) const; + Error get_uncompressed_item_data(heif_item_id ID, std::vector* data) const; - Error get_item_data(heif_item_id ID, std::vector* out_data, heif_metadata_compression* out_compression) const; + Error append_data_from_iloc(heif_item_id ID, std::vector& out_data, uint64_t offset, uint64_t size) const; + + Error append_data_from_iloc(heif_item_id ID, std::vector& out_data) const { + return append_data_from_iloc(ID, out_data, 0, std::numeric_limits::max()); + } + + Error get_item_data(heif_item_id ID, std::vector *out_data, heif_metadata_compression* out_compression) const; std::shared_ptr get_ftyp_box() { return m_ftyp_box; } @@ -96,6 +110,8 @@ class HeifFile std::shared_ptr get_iref_box() { return m_iref_box; } + std::shared_ptr get_iref_box() const { return m_iref_box; } + std::shared_ptr get_ipco_box() { return m_ipco_box; } std::shared_ptr get_ipco_box() const { return m_ipco_box; } @@ -104,6 +120,10 @@ class HeifFile std::shared_ptr get_ipma_box() const { return m_ipma_box; } + std::shared_ptr get_grpl_box() const { return m_grpl_box; } + + std::shared_ptr get_entity_group(heif_entity_group_id id); + Error get_properties(heif_item_id imageID, std::vector>& properties) const; @@ -125,12 +145,6 @@ class HeifFile return nullptr; } - heif_chroma get_image_chroma_from_configuration(heif_item_id imageID) const; - - int get_luma_bits_per_pixel_from_configuration(heif_item_id imageID) const; - - int get_chroma_bits_per_pixel_from_configuration(heif_item_id imageID) const; - std::string debug_dump_boxes() const; @@ -138,49 +152,21 @@ class HeifFile heif_item_id get_unused_item_id() const; - heif_item_id add_new_image(const char* item_type); - - heif_item_id add_new_hidden_image(const char* item_type); - - - std::shared_ptr add_new_infe_box(const char* item_type); - - - void add_av1C_property(heif_item_id id, const Box_av1C::configuration& config); + heif_item_id add_new_image(uint32_t item_type); - void add_vvcC_property(heif_item_id id); + std::shared_ptr add_new_infe_box(uint32_t item_type); - Error append_vvcC_nal_data(heif_item_id id, const std::vector& data); - - Error append_vvcC_nal_data(heif_item_id id, const uint8_t* data, size_t size); - - Error set_vvcC_configuration(heif_item_id id, const Box_vvcC::configuration& config); - - void add_hvcC_property(heif_item_id id); - - Error append_hvcC_nal_data(heif_item_id id, const std::vector& data); - - Error append_hvcC_nal_data(heif_item_id id, const uint8_t* data, size_t size); - - Error set_hvcC_configuration(heif_item_id id, const Box_hvcC::configuration& config); - - Error set_av1C_configuration(heif_item_id id, const Box_av1C::configuration& config); - - std::shared_ptr add_j2kH_property(heif_item_id id); - - void add_ispe_property(heif_item_id id, uint32_t width, uint32_t height); - - void add_clap_property(heif_item_id id, uint32_t clap_width, uint32_t clap_height, - uint32_t image_width, uint32_t image_height); + void add_ispe_property(heif_item_id id, uint32_t width, uint32_t height, bool essential); // set irot/imir according to heif_orientation void add_orientation_properties(heif_item_id id, heif_orientation); - void add_pixi_property(heif_item_id id, uint8_t c1, uint8_t c2 = 0, uint8_t c3 = 0); - + // TODO: can we remove the 'essential' parameter and take this from the box? Or is that depending on the context? heif_property_id add_property(heif_item_id id, const std::shared_ptr& property, bool essential); - Result add_infe(const char* item_type, const uint8_t* data, size_t size); + heif_property_id add_property_without_deduplication(heif_item_id id, const std::shared_ptr& property, bool essential); + + Result add_infe(uint32_t item_type, const uint8_t* data, size_t size); Result add_infe_mime(const char* content_type, heif_metadata_compression content_encoding, const uint8_t* data, size_t size); @@ -192,21 +178,20 @@ class HeifFile Error set_precompressed_item_data(const std::shared_ptr& item, const uint8_t* data, size_t size, std::string content_encoding); - void append_iloc_data(heif_item_id id, const std::vector& nal_packets, uint8_t construction_method = 0); + void append_iloc_data(heif_item_id id, const std::vector& nal_packets, uint8_t construction_method); - void append_iloc_data_with_4byte_size(heif_item_id id, const uint8_t* data, size_t size); + void replace_iloc_data(heif_item_id id, uint64_t offset, const std::vector& data, uint8_t construction_method = 0); void set_primary_item_id(heif_item_id id); void add_iref_reference(heif_item_id from, uint32_t type, const std::vector& to); - void set_auxC_property(heif_item_id id, const std::string& type); + void set_iref_reference(heif_item_id from, uint32_t type, int reference_idx, heif_item_id to_item); - void set_color_profile(heif_item_id id, const std::shared_ptr& profile); + void add_entity_group_box(const std::shared_ptr& entity_group_box); - // TODO: the hdlr box is probably not the right place for this. Into which box should we write comments? - void set_hdlr_library_info(const std::string& encoder_plugin_version); + void set_auxC_property(heif_item_id id, const std::string& type); #if defined(__MINGW32__) || defined(__MINGW64__) || defined(_MSC_VER) static std::wstring convert_utf8_path_to_utf16(std::string pathutf8); @@ -217,6 +202,8 @@ class HeifFile mutable std::mutex m_read_mutex; #endif + std::shared_ptr m_file_layout; + std::shared_ptr m_input_stream; std::vector > m_top_level_boxes; @@ -232,16 +219,15 @@ class HeifFile std::shared_ptr m_iref_box; std::shared_ptr m_pitm_box; std::shared_ptr m_iinf_box; + std::shared_ptr m_grpl_box; std::shared_ptr m_iprp_box; std::map > m_infe_boxes; - // list of image items (does not include hidden images or Exif data) - //std::vector m_valid_image_IDs; + const heif_security_limits* m_limits = nullptr; - - Error parse_heif_file(BitstreamRange& bitstream); + Error parse_heif_file(); Error check_for_ref_cycle(heif_item_id ID, const std::shared_ptr& iref_box) const; @@ -249,8 +235,6 @@ class HeifFile Error check_for_ref_cycle_recursion(heif_item_id ID, const std::shared_ptr& iref_box, std::unordered_set& parent_items) const; - - int jpeg_get_bits_per_pixel(heif_item_id imageID) const; }; #endif diff --git a/libheif/file_layout.cc b/libheif/file_layout.cc new file mode 100644 index 0000000000..dcc1947c17 --- /dev/null +++ b/libheif/file_layout.cc @@ -0,0 +1,174 @@ +/* + * HEIF codec. + * Copyright (c) 2024 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 "file_layout.h" + + +FileLayout::FileLayout() +{ + auto ftyp = std::make_shared(); + ftyp->set_output_position(0); + m_boxes.push_back(ftyp); + + // TODO: these variables are not used yet + (void)m_writeMode; + (void)m_file_size; +} + + +Error FileLayout::read(const std::shared_ptr& stream, const heif_security_limits* limits) +{ + m_boxes.clear(); + + m_stream_reader = stream; + + // --- read initial range, large enough to cover 'ftyp' box + + m_max_length = stream->request_range(0, INITIAL_FTYP_REQUEST); + + if (m_max_length < MAXIMUM_BOX_HEADER_SIZE) { + return {heif_error_Invalid_input, + heif_suberror_Unspecified, + "File size too small."}; + } + + // --- read 'ftyp' box header + + BitstreamRange ftyp_hdr_range(m_stream_reader, m_max_length); + BoxHeader ftyp_header; + Error err; + err = ftyp_header.parse_header(ftyp_hdr_range); + if (err) { + return err; + } + + // --- check whether it is a valid 'ftyp' box header + + if (ftyp_header.get_short_type() != fourcc("ftyp")) { + return {heif_error_Invalid_input, + heif_suberror_No_ftyp_box, + "File does not start with 'ftyp' box."}; + } + + uint64_t ftyp_size = ftyp_header.get_box_size(); + + if (ftyp_size == 0) { + return {heif_error_Invalid_input, + heif_suberror_No_ftyp_box, + "ftyp box shall not be the only box in the file"}; + } + + if (ftyp_size > m_max_length) { + return {heif_error_Invalid_input, + heif_suberror_No_ftyp_box, + "ftyp box larger than initial read range"}; + } + + // --- read the 'ftyp' box + + BitstreamRange ftyp_range(m_stream_reader, 0, ftyp_size); + std::shared_ptr ftyp_box; + err = Box::read(ftyp_range, &ftyp_box, limits); + + m_boxes.push_back(ftyp_box); + m_ftyp_box = std::dynamic_pointer_cast(ftyp_box); + + + // --- skip through box headers until we find the 'meta' box + + uint64_t next_box_start = ftyp_size; + + for (;;) { + // TODO: overflow + uint64_t next_box_header_end = next_box_start + MAXIMUM_BOX_HEADER_SIZE; + if (next_box_header_end > m_max_length) { + m_max_length = stream->request_range(next_box_start, next_box_header_end); + } + + if (next_box_header_end > m_max_length) { + return {heif_error_Invalid_input, + heif_suberror_Unspecified, + "Insufficient input data"}; + } + + BitstreamRange box_range(m_stream_reader, next_box_start, m_max_length); + BoxHeader box_header; + err = box_header.parse_header(box_range); + if (err) { + return err; + } + + if (box_header.get_short_type() == fourcc("meta")) { + const uint64_t meta_box_start = next_box_start; + if (box_header.get_box_size() == 0) { + // TODO: get file-size from stream and compute box size + return {heif_error_Invalid_input, + heif_suberror_No_meta_box, + "Cannot read meta box with unspecified size"}; + } + + // TODO: overflow + uint64_t end_of_meta_box = meta_box_start + box_header.get_box_size(); + if (m_max_length < end_of_meta_box) { + m_max_length = m_stream_reader->request_range(meta_box_start, end_of_meta_box); + } + + if (m_max_length < end_of_meta_box) { + return {heif_error_Invalid_input, + heif_suberror_No_meta_box, + "Cannot read full meta box"}; + } + + BitstreamRange meta_box_range(m_stream_reader, meta_box_start, end_of_meta_box); + std::shared_ptr meta_box; + err = Box::read(meta_box_range, &meta_box, limits); + if (err) { + return err; + } + + m_boxes.push_back(meta_box); + m_meta_box = std::dynamic_pointer_cast(meta_box); + break; + } + + if (box_header.get_box_size() == 0) { + return {heif_error_Invalid_input, + heif_suberror_No_meta_box, + "No meta box found"}; + } + + // TODO: overflow + next_box_start = next_box_start + box_header.get_box_size(); + } + + return Error::Ok; +} + + +void FileLayout::set_write_mode(WriteMode writeMode, const std::shared_ptr& writer) +{ + +} + + +Error FileLayout::write(std::shared_ptr& stream) +{ + return Error::Ok; +} diff --git a/libheif/file_layout.h b/libheif/file_layout.h new file mode 100644 index 0000000000..5700a130d0 --- /dev/null +++ b/libheif/file_layout.h @@ -0,0 +1,81 @@ +/* + * HEIF codec. + * Copyright (c) 2024 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_FILELAYOUT_H +#define LIBHEIF_FILELAYOUT_H + +#include "error.h" +#include "bitstream.h" +#include "box.h" +#include +#include + + +class FileLayout +{ +public: + enum class WriteMode { + Streaming, // 'mdat' data will be written to output immediately + Floating, // 'mdat' data will be held in memory until written + TmpFile // 'mdat' data will be written to temporary file and copied into final file + }; + + // Generate a file in WriteMode::Floating + FileLayout(); + + Error read(const std::shared_ptr& stream, const heif_security_limits* limits); + + // For WriteMode::Streaming, writer cannot be null. + void set_write_mode(WriteMode writeMode, const std::shared_ptr& writer = nullptr); + + // For WriteMode::Streaming, stream must be null. + Error write(std::shared_ptr& stream); + + + // --- access to boxes + + std::shared_ptr get_ftyp_box() { return m_ftyp_box; } + + std::shared_ptr get_meta_box() { return m_meta_box; } + +private: + WriteMode m_writeMode = WriteMode::Floating; + + const static uint64_t INVALID_FILE_SIZE = 0xFFFFFFFFFFFFFFFF; + + uint64_t m_file_size = INVALID_FILE_SIZE; + + // the first one is always 'ftyp' + std::vector> m_boxes; // TODO: do we need this ? + + std::shared_ptr m_ftyp_box; + std::shared_ptr m_meta_box; + + + uint64_t m_max_length = 0; // Length seen so far. It can grow over time. + + std::shared_ptr m_stream_reader; + std::shared_ptr m_stream_writer; + + static const uint64_t INITIAL_FTYP_REQUEST = 1024; // should be enough to read ftyp and next box header + static const uint16_t MAXIMUM_BOX_HEADER_SIZE = 32; +}; + +#endif //LIBHEIF_FILELAYOUT_H diff --git a/libheif/image-items/avc.cc b/libheif/image-items/avc.cc new file mode 100644 index 0000000000..4efdb3b2e4 --- /dev/null +++ b/libheif/image-items/avc.cc @@ -0,0 +1,144 @@ +/* + * HEIF AVC codec. + * Copyright (c) 2023 Brad Hards + * + * 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 "avc.h" +#include +#include +#include +#include +#include "file.h" +#include "context.h" +#include "codecs/avc_dec.h" +#include "codecs/avc_boxes.h" + + +Result ImageItem_AVC::encode(const std::shared_ptr& image, + struct heif_encoder* encoder, + const struct heif_encoding_options& options, + enum heif_image_input_class input_class) +{ +#if 0 + CodedImageData codedImage; + + auto hvcC = std::make_shared(); + + heif_image c_api_image; + c_api_image.image = image; + + struct heif_error err = encoder->plugin->encode_image(encoder->encoder, &c_api_image, input_class); + if (err.code) { + return Error(err.code, + err.subcode, + err.message); + } + + int encoded_width = 0; + int encoded_height = 0; + + for (;;) { + uint8_t* data; + int size; + + encoder->plugin->get_compressed_data(encoder->encoder, &data, &size, nullptr); + + if (data == nullptr) { + break; + } + + + const uint8_t NAL_SPS = 33; + + if ((data[0] >> 1) == NAL_SPS) { + Box_hvcC::configuration config; + + parse_sps_for_hvcC_configuration(data, size, &config, &encoded_width, &encoded_height); + + hvcC->set_configuration(config); + + codedImage.encoded_image_width = encoded_width; + codedImage.encoded_image_height = encoded_height; + } + + switch (data[0] >> 1) { + case 0x20: + case 0x21: + case 0x22: + hvcC->append_nal_data(data, size); + break; + + default: + codedImage.append_with_4bytes_size(data, size); + // m_heif_file->append_iloc_data_with_4byte_size(image_id, data, size); + } + } + + if (!encoded_width || !encoded_height) { + return Error(heif_error_Encoder_plugin_error, + heif_suberror_Invalid_image_size); + } + + codedImage.properties.push_back(hvcC); + + + // Make sure that the encoder plugin works correctly and the encoded image has the correct size. + + if (encoder->plugin->plugin_api_version >= 3 && + encoder->plugin->query_encoded_size != nullptr) { + uint32_t check_encoded_width = image->get_width(), check_encoded_height = image->get_height(); + + encoder->plugin->query_encoded_size(encoder->encoder, + image->get_width(), image->get_height(), + &check_encoded_width, + &check_encoded_height); + + assert((int)check_encoded_width == encoded_width); + assert((int)check_encoded_height == encoded_height); + } + + return codedImage; +#endif + assert(false); // TODO + return {}; +} + + +std::shared_ptr ImageItem_AVC::get_decoder() const +{ + return m_decoder; +} + + +Error ImageItem_AVC::on_load_file() +{ + auto avcC_box = get_file()->get_property(get_id()); + if (!avcC_box) { + return Error{heif_error_Invalid_input, + heif_suberror_No_av1C_box}; + } + + m_decoder = std::make_shared(avcC_box); + + DataExtent extent; + extent.set_from_image_item(get_context()->get_heif_file(), get_id()); + + m_decoder->set_data_extent(extent); + + return Error::Ok; +} diff --git a/libheif/image-items/avc.h b/libheif/image-items/avc.h new file mode 100644 index 0000000000..d0033b615e --- /dev/null +++ b/libheif/image-items/avc.h @@ -0,0 +1,62 @@ +/* + * HEIF AVC codec. + * Copyright (c) 2023 Brad Hards + * + * 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 HEIF_AVC_H +#define HEIF_AVC_H + +#include "box.h" +#include "error.h" +#include +#include +#include +#include +#include "image_item.h" + + +class ImageItem_AVC : public ImageItem +{ +public: + ImageItem_AVC(HeifContext* ctx, heif_item_id id) : ImageItem(ctx, id) {} + + ImageItem_AVC(HeifContext* ctx) : ImageItem(ctx) {} + + uint32_t get_infe_type() const override { return fourcc("avc1"); } + + const char* get_auxC_alpha_channel_type() const override { return "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"; } + + const heif_color_profile_nclx* get_forced_output_nclx() const override { return nullptr; } + + heif_compression_format get_compression_format() const override { return heif_compression_AVC; } + + Error on_load_file() override; + +protected: + std::shared_ptr get_decoder() const override; + +public: + Result encode(const std::shared_ptr& image, + struct heif_encoder* encoder, + const struct heif_encoding_options& options, + enum heif_image_input_class input_class) override; + + std::shared_ptr m_decoder; +}; + +#endif diff --git a/libheif/image-items/avif.cc b/libheif/image-items/avif.cc new file mode 100644 index 0000000000..c2cadb0059 --- /dev/null +++ b/libheif/image-items/avif.cc @@ -0,0 +1,113 @@ +/* + * HEIF codec. + * Copyright (c) 2017 Dirk Farin + * + * This file is part of libheif. + * + * libheif is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * libheif is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libheif. If not, see . + */ + +#include "pixelimage.h" +#include "avif.h" +#include "codecs/avif_dec.h" +#include "codecs/avif_boxes.h" +#include "bitstream.h" +#include "common_utils.h" +#include "libheif/api_structs.h" +#include "file.h" +#include +#include +#include +#include + +// https://aomediacodec.github.io/av1-spec/av1-spec.pdf + + + +Error ImageItem_AVIF::on_load_file() +{ + auto av1C_box = get_file()->get_property(get_id()); + if (!av1C_box) { + return Error{heif_error_Invalid_input, + heif_suberror_No_av1C_box}; + } + + m_decoder = std::make_shared(av1C_box); + + DataExtent extent; + extent.set_from_image_item(get_context()->get_heif_file(), get_id()); + + m_decoder->set_data_extent(extent); + + return Error::Ok; +} + + +Result ImageItem_AVIF::encode(const std::shared_ptr& image, + struct heif_encoder* encoder, + const struct heif_encoding_options& options, + enum heif_image_input_class input_class) +{ + CodedImageData codedImage; + + Box_av1C::configuration config; + + // Fill preliminary av1C in case we cannot parse the sequence_header() correctly in the code below. + // TODO: maybe we can remove this later. + fill_av1C_configuration(&config, image); + + heif_image c_api_image; + c_api_image.image = image; + + struct heif_error err = encoder->plugin->encode_image(encoder->encoder, &c_api_image, input_class); + if (err.code) { + return Error(err.code, + err.subcode, + err.message); + } + + for (;;) { + uint8_t* data; + int size; + + encoder->plugin->get_compressed_data(encoder->encoder, &data, &size, nullptr); + + bool found_config = fill_av1C_configuration_from_stream(&config, data, size); + (void) found_config; + + if (data == nullptr) { + break; + } + + codedImage.append(data, size); + } + + auto av1C = std::make_shared(); + av1C->set_configuration(config); + codedImage.properties.push_back(av1C); + + return codedImage; +} + + +Result> ImageItem_AVIF::read_bitstream_configuration_data(heif_item_id itemId) const +{ + return m_decoder->read_bitstream_configuration_data(); +} + + +std::shared_ptr ImageItem_AVIF::get_decoder() const +{ + return m_decoder; +} diff --git a/libheif/image-items/avif.h b/libheif/image-items/avif.h new file mode 100644 index 0000000000..65b98581b1 --- /dev/null +++ b/libheif/image-items/avif.h @@ -0,0 +1,68 @@ +/* + * HEIF codec. + * Copyright (c) 2017 Dirk Farin + * + * This file is part of libheif. + * + * libheif is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * libheif is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libheif. If not, see . + */ + +#ifndef HEIF_AVIF_H +#define HEIF_AVIF_H + +#include +#include +#include +#include +#include + +#include "libheif/heif.h" +#include "box.h" +#include "error.h" +#include "image_item.h" + + +class ImageItem_AVIF : public ImageItem +{ +public: + ImageItem_AVIF(HeifContext* ctx, heif_item_id id) : ImageItem(ctx, id) {} + + ImageItem_AVIF(HeifContext* ctx) : ImageItem(ctx) {} + + uint32_t get_infe_type() const override { return fourcc("av01"); } + + const char* get_auxC_alpha_channel_type() const override { return "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"; } + + const heif_color_profile_nclx* get_forced_output_nclx() const override { return nullptr; } + + heif_compression_format get_compression_format() const override { return heif_compression_AV1; } + + Error on_load_file() override; + +public: + Result encode(const std::shared_ptr& image, + struct heif_encoder* encoder, + const struct heif_encoding_options& options, + enum heif_image_input_class input_class) override; + +protected: + Result> read_bitstream_configuration_data(heif_item_id itemId) const override; + + std::shared_ptr get_decoder() const override; + +private: + std::shared_ptr m_decoder; +}; + +#endif diff --git a/libheif/image-items/grid.cc b/libheif/image-items/grid.cc new file mode 100644 index 0000000000..d5317e4395 --- /dev/null +++ b/libheif/image-items/grid.cc @@ -0,0 +1,729 @@ +/* + * HEIF codec. + * Copyright (c) 2024 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 "grid.h" +#include "context.h" +#include "file.h" +#include +#include +#include +#include +#include +#include +#include "security_limits.h" + + +Error ImageGrid::parse(const std::vector& data) +{ + if (data.size() < 8) { + return {heif_error_Invalid_input, + heif_suberror_Invalid_grid_data, + "Less than 8 bytes of data"}; + } + + uint8_t version = data[0]; + if (version != 0) { + std::stringstream sstr; + sstr << "Grid image version " << ((int) version) << " is not supported"; + return {heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + sstr.str()}; + } + + uint8_t flags = data[1]; + int field_size = ((flags & 1) ? 32 : 16); + + m_rows = static_cast(data[2] + 1); + m_columns = static_cast(data[3] + 1); + + if (field_size == 32) { + if (data.size() < 12) { + return {heif_error_Invalid_input, + heif_suberror_Invalid_grid_data, + "Grid image data incomplete"}; + } + + m_output_width = ((data[4] << 24) | + (data[5] << 16) | + (data[6] << 8) | + (data[7])); + + m_output_height = ((data[8] << 24) | + (data[9] << 16) | + (data[10] << 8) | + (data[11])); + } + else { + m_output_width = ((data[4] << 8) | + (data[5])); + + m_output_height = ((data[6] << 8) | + (data[7])); + } + + return Error::Ok; +} + + +std::vector ImageGrid::write() const +{ + int field_size; + + if (m_output_width > 0xFFFF || + m_output_height > 0xFFFF) { + field_size = 32; + } + else { + field_size = 16; + } + + std::vector data(field_size == 16 ? 8 : 12); + + data[0] = 0; // version + + uint8_t flags = 0; + if (field_size == 32) { + flags |= 1; + } + + data[1] = flags; + data[2] = (uint8_t) (m_rows - 1); + data[3] = (uint8_t) (m_columns - 1); + + if (field_size == 32) { + data[4] = (uint8_t) ((m_output_width >> 24) & 0xFF); + data[5] = (uint8_t) ((m_output_width >> 16) & 0xFF); + data[6] = (uint8_t) ((m_output_width >> 8) & 0xFF); + data[7] = (uint8_t) ((m_output_width) & 0xFF); + + data[8] = (uint8_t) ((m_output_height >> 24) & 0xFF); + data[9] = (uint8_t) ((m_output_height >> 16) & 0xFF); + data[10] = (uint8_t) ((m_output_height >> 8) & 0xFF); + data[11] = (uint8_t) ((m_output_height) & 0xFF); + } + else { + data[4] = (uint8_t) ((m_output_width >> 8) & 0xFF); + data[5] = (uint8_t) ((m_output_width) & 0xFF); + + data[6] = (uint8_t) ((m_output_height >> 8) & 0xFF); + data[7] = (uint8_t) ((m_output_height) & 0xFF); + } + + return data; +} + + +std::string ImageGrid::dump() const +{ + std::ostringstream sstr; + + sstr << "rows: " << m_rows << "\n" + << "columns: " << m_columns << "\n" + << "output width: " << m_output_width << "\n" + << "output height: " << m_output_height << "\n"; + + return sstr.str(); +} + + +ImageItem_Grid::ImageItem_Grid(HeifContext* ctx) + : ImageItem(ctx) +{ +} + + +ImageItem_Grid::ImageItem_Grid(HeifContext* ctx, heif_item_id id) + : ImageItem(ctx, id) +{ +} + + +Error ImageItem_Grid::on_load_file() +{ + Error err = read_grid_spec(); + if (err) { + return err; + } + + return Error::Ok; +} + + +Error ImageItem_Grid::read_grid_spec() +{ + auto heif_file = get_context()->get_heif_file(); + + std::vector grid_data; + Error err = heif_file->get_uncompressed_item_data(get_id(), &grid_data); + if (err) { + return err; + } + + err = m_grid_spec.parse(grid_data); + if (err) { + return err; + } + + //std::cout << grid.dump(); + + + auto iref_box = heif_file->get_iref_box(); + + if (!iref_box) { + return {heif_error_Invalid_input, + heif_suberror_No_iref_box, + "No iref box available, but needed for grid image"}; + } + + m_grid_tile_ids = iref_box->get_references(get_id(), fourcc("dimg")); + + if ((int) m_grid_tile_ids.size() != m_grid_spec.get_rows() * m_grid_spec.get_columns()) { + std::stringstream sstr; + sstr << "Tiled image with " << m_grid_spec.get_rows() << "x" << m_grid_spec.get_columns() << "=" + << (m_grid_spec.get_rows() * m_grid_spec.get_columns()) << " tiles, but only " + << m_grid_tile_ids.size() << " tile images in file"; + + return {heif_error_Invalid_input, + heif_suberror_Missing_grid_images, + sstr.str()}; + } + + return Error::Ok; +} + + +Result> ImageItem_Grid::decode_compressed_image(const struct heif_decoding_options& options, + bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0) const +{ + if (decode_tile_only) { + return decode_grid_tile(options, tile_x0, tile_y0); + } + else { + return decode_full_grid_image(options); + } +} + + +Result> ImageItem_Grid::decode_full_grid_image(const heif_decoding_options& options) const +{ + std::shared_ptr img; // the decoded image + + const ImageGrid& grid = get_grid_spec(); + + + // --- check that all image IDs are valid images + + const std::vector& image_references = get_grid_tiles(); + + for (heif_item_id tile_id : image_references) { + if (!get_context()->is_image(tile_id)) { + std::stringstream sstr; + sstr << "Tile image ID=" << tile_id << " is not a proper image."; + + return Error(heif_error_Invalid_input, + heif_suberror_Missing_grid_images, + sstr.str()); + } + } + + //auto pixi = get_file()->get_property(get_id()); + + const uint32_t w = grid.get_width(); + const uint32_t h = grid.get_height(); + + Error err = check_for_valid_image_size(get_context()->get_security_limits(), w, h); + if (err) { + return err; + } + + uint32_t y0 = 0; + int reference_idx = 0; + +#if ENABLE_PARALLEL_TILE_DECODING + // remember which tile to put where into the image + struct tile_data + { + heif_item_id tileID; + uint32_t x_origin, y_origin; + }; + + std::deque tiles; + if (get_context()->get_max_decoding_threads() > 0) + tiles.resize(grid.get_rows() * grid.get_columns()); + + std::deque > errs; +#endif + + uint32_t tile_width = 0; + uint32_t tile_height = 0; + + if (options.start_progress) { + options.start_progress(heif_progress_step_total, grid.get_rows() * grid.get_columns(), options.progress_user_data); + } + if (options.on_progress) { + options.on_progress(heif_progress_step_total, 0, options.progress_user_data); + } + + int progress_counter = 0; + + for (uint32_t y = 0; y < grid.get_rows(); y++) { + uint32_t x0 = 0; + + for (uint32_t x = 0; x < grid.get_columns(); x++) { + + heif_item_id tileID = image_references[reference_idx]; + + std::shared_ptr tileImg = get_context()->get_image(tileID, true); + if (!tileImg) { + return Error{heif_error_Invalid_input, + heif_suberror_Missing_grid_images, + "Nonexistent grid image referenced"}; + } + if (auto error = tileImg->get_item_error()) { + return error; + } + + uint32_t src_width = tileImg->get_width(); + uint32_t src_height = tileImg->get_height(); + err = check_for_valid_image_size(get_context()->get_security_limits(), src_width, src_height); + if (err) { + return err; + } + + if (src_width < grid.get_width() / grid.get_columns() || + src_height < grid.get_height() / grid.get_rows()) { + return Error{heif_error_Invalid_input, + heif_suberror_Invalid_grid_data, + "Grid tiles do not cover whole image"}; + } + + if (x == 0 && y == 0) { + // remember size of first tile and compare all other tiles against this + tile_width = src_width; + tile_height = src_height; + } + else if (src_width != tile_width || src_height != tile_height) { + return Error{heif_error_Invalid_input, + heif_suberror_Invalid_grid_data, + "Grid tiles have different sizes"}; + } + +#if ENABLE_PARALLEL_TILE_DECODING + if (get_context()->get_max_decoding_threads() > 0) + tiles[x + y * grid.get_columns()] = tile_data{tileID, x0, y0}; + else +#else + if (1) +#endif + { + err = decode_and_paste_tile_image(tileID, x0, y0, img, options, progress_counter); + if (err) { + return err; + } + } + + x0 += src_width; + + reference_idx++; + } + + y0 += tile_height; + } + +#if ENABLE_PARALLEL_TILE_DECODING + if (get_context()->get_max_decoding_threads() > 0) { + // Process all tiles in a set of background threads. + // Do not start more than the maximum number of threads. + + while (!tiles.empty()) { + + // If maximum number of threads running, wait until first thread finishes + + if (errs.size() >= (size_t) get_context()->get_max_decoding_threads()) { + Error e = errs.front().get(); + if (e) { + return e; + } + + errs.pop_front(); + } + + + // Start a new decoding thread + + tile_data data = tiles.front(); + tiles.pop_front(); + + errs.push_back(std::async(std::launch::async, + &ImageItem_Grid::decode_and_paste_tile_image, this, + data.tileID, data.x_origin, data.y_origin, std::ref(img), options, + std::ref(progress_counter))); + } + + // check for decoding errors in remaining tiles + + while (!errs.empty()) { + Error e = errs.front().get(); + if (e) { + return e; + } + + errs.pop_front(); + } + } +#endif + + if (options.end_progress) { + options.end_progress(heif_progress_step_total, options.progress_user_data); + } + + return img; +} + +Error ImageItem_Grid::decode_and_paste_tile_image(heif_item_id tileID, uint32_t x0, uint32_t y0, + std::shared_ptr& inout_image, + const heif_decoding_options& options, + int& progress_counter) const +{ + std::shared_ptr tile_img; + + auto tileItem = get_context()->get_image(tileID, true); + assert(tileItem); + if (auto error = tileItem->get_item_error()) { + return error; + } + + auto decodeResult = tileItem->decode_image(options, false, 0, 0); + if (decodeResult.error) { + return decodeResult.error; + } + + tile_img = decodeResult.value; + + uint32_t w = get_grid_spec().get_width(); + uint32_t h = get_grid_spec().get_height(); + + // --- generate the image canvas for combining all the tiles + + if (!inout_image) { // this if avoids that we normally have to lock a mutex + static std::mutex createImageMutex; + std::lock_guard lock(createImageMutex); + + if (!inout_image) { + inout_image = std::make_shared(); + inout_image->create_clone_image_at_new_size(tile_img, w, h); + + // Fill alpha plane with opaque in case not all tiles have alpha planes + + if (inout_image->has_channel(heif_channel_Alpha)) { + uint16_t alpha_bpp = inout_image->get_bits_per_pixel(heif_channel_Alpha); + assert(alpha_bpp <= 16); + + auto alpha_default_value = static_cast((1UL << alpha_bpp) - 1UL); + inout_image->fill_plane(heif_channel_Alpha, alpha_default_value); + } + } + } + + // --- copy tile into output image + + heif_chroma chroma = inout_image->get_chroma_format(); + + if (chroma != tile_img->get_chroma_format()) { + return {heif_error_Invalid_input, + heif_suberror_Wrong_tile_image_chroma_format, + "Image tile has different chroma format than combined image"}; + } + + + inout_image->copy_image_to(tile_img, x0, y0); + + if (options.on_progress) { + static std::mutex progressMutex; + std::lock_guard lock(progressMutex); + + options.on_progress(heif_progress_step_total, ++progress_counter, options.progress_user_data); + } + + return Error::Ok; +} + + +Result> ImageItem_Grid::decode_grid_tile(const heif_decoding_options& options, uint32_t tx, uint32_t ty) const +{ + uint32_t idx = ty * m_grid_spec.get_columns() + tx; + + assert(idx < m_grid_tile_ids.size()); + + heif_item_id tile_id = m_grid_tile_ids[idx]; + std::shared_ptr tile_item = get_context()->get_image(tile_id, true); + if (auto error = tile_item->get_item_error()) { + return error; + } + + return tile_item->decode_compressed_image(options, true, tx, ty); +} + + +void ImageItem_Grid::set_grid_tile_id(uint32_t tile_x, uint32_t tile_y, heif_item_id id) +{ + uint32_t idx = tile_y * m_grid_spec.get_columns() + tile_x; + m_grid_tile_ids[idx] = id; +} + + +heif_image_tiling ImageItem_Grid::get_heif_image_tiling() const +{ + heif_image_tiling tiling{}; + + const ImageGrid& gridspec = get_grid_spec(); + tiling.num_columns = gridspec.get_columns(); + tiling.num_rows = gridspec.get_rows(); + + tiling.image_width = gridspec.get_width(); + tiling.image_height = gridspec.get_height(); + tiling.number_of_extra_dimensions = 0; + + auto tile_ids = get_grid_tiles(); + if (!tile_ids.empty() && tile_ids[0] != 0) { + heif_item_id tile0_id = tile_ids[0]; + auto tile0 = get_context()->get_image(tile0_id, true); + if (tile0->get_item_error()) { + return tiling; + } + + tiling.tile_width = tile0->get_width(); + tiling.tile_height = tile0->get_height(); + } + else { + tiling.tile_width = 0; + tiling.tile_height = 0; + } + + return tiling; +} + + +void ImageItem_Grid::get_tile_size(uint32_t& w, uint32_t& h) const +{ + heif_item_id first_tile_id = get_grid_tiles()[0]; + auto tile = get_context()->get_image(first_tile_id, true); + if (tile->get_item_error()) { + w = h = 0; + } + + w = tile->get_width(); + h = tile->get_height(); +} + + + +int ImageItem_Grid::get_luma_bits_per_pixel() const +{ + heif_item_id child; + Error err = get_context()->get_id_of_non_virtual_child_image(get_id(), child); + if (err) { + return -1; + } + + auto image = get_context()->get_image(child, true); + if (!image) { + return -1; + } + + return image->get_luma_bits_per_pixel(); +} + + +int ImageItem_Grid::get_chroma_bits_per_pixel() const +{ + heif_item_id child; + Error err = get_context()->get_id_of_non_virtual_child_image(get_id(), child); + if (err) { + return -1; + } + + auto image = get_context()->get_image(child, true); + return image->get_chroma_bits_per_pixel(); +} + +std::shared_ptr ImageItem_Grid::get_decoder() const +{ + heif_item_id child; + Error err = get_context()->get_id_of_non_virtual_child_image(get_id(), child); + if (err) { + return nullptr; + } + + auto image = get_context()->get_image(child, true); + if (image->get_item_error()) { + return nullptr; + } + + return image->get_decoder(); +} + + +Result> ImageItem_Grid::add_new_grid_item(HeifContext* ctx, + uint32_t output_width, + uint32_t output_height, + uint16_t tile_rows, + uint16_t tile_columns, + const struct heif_encoding_options* encoding_options) +{ + std::shared_ptr grid_image; + if (tile_rows > 0xFFFF / tile_columns) { + return Error{heif_error_Usage_error, + heif_suberror_Unspecified, + "Too many tiles (maximum: 65535)"}; + } + + // Create ImageGrid + + ImageGrid grid; + grid.set_num_tiles(tile_columns, tile_rows); + grid.set_output_size(output_width, output_height); + std::vector grid_data = grid.write(); + + // Create Grid Item + + std::shared_ptr file = ctx->get_heif_file(); + heif_item_id grid_id = file->add_new_image(fourcc("grid")); + grid_image = std::make_shared(ctx, grid_id); + grid_image->set_encoding_options(encoding_options); + grid_image->set_grid_spec(grid); + grid_image->set_resolution(output_width, output_height); + + ctx->insert_image_item(grid_id, grid_image); + const int construction_method = 1; // 0=mdat 1=idat + file->append_iloc_data(grid_id, grid_data, construction_method); + + // generate dummy grid item IDs (0) + std::vector tile_ids; + tile_ids.resize(tile_rows * tile_columns); + + // Connect tiles to grid + file->add_iref_reference(grid_id, fourcc("dimg"), tile_ids); + + // Add ISPE property + file->add_ispe_property(grid_id, output_width, output_height, false); + + // PIXI property will be added when the first tile is set + + // Set Brands + //m_heif_file->set_brand(encoder->plugin->compression_format, + // grid_image->is_miaf_compatible()); + + return grid_image; +} + + +Error ImageItem_Grid::add_image_tile(heif_item_id grid_id, uint32_t tile_x, uint32_t tile_y, + const std::shared_ptr& image, + struct heif_encoder* encoder) +{ + auto encoding_options = get_encoding_options(); + + std::shared_ptr encoded_image; + Error error = get_context()->encode_image(image, + encoder, + *encoding_options, + heif_image_input_class_normal, + encoded_image); + if (error != Error::Ok) { + return error; + } + + auto file = get_file(); + file->get_infe_box(encoded_image->get_id())->set_hidden_item(true); // grid tiles are hidden items + + // Assign tile to grid + heif_image_tiling tiling = get_heif_image_tiling(); + file->set_iref_reference(grid_id, fourcc("dimg"), tile_y * tiling.num_columns + tile_x, encoded_image->get_id()); + + set_grid_tile_id(tile_x, tile_y, encoded_image->get_id()); + + // Add PIXI property (copy from first tile) + auto pixi = file->get_property(encoded_image->get_id()); + file->add_property(grid_id, pixi, true); + + return Error::Ok; +} + + +Result> ImageItem_Grid::add_and_encode_full_grid(HeifContext* ctx, + const std::vector>& tiles, + uint16_t rows, + uint16_t columns, + struct heif_encoder* encoder, + const struct heif_encoding_options& options) +{ + std::shared_ptr griditem; + + // Create ImageGrid + ImageGrid grid; + grid.set_num_tiles(columns, rows); + uint32_t tile_width = tiles[0]->get_width(heif_channel_interleaved); + uint32_t tile_height = tiles[0]->get_height(heif_channel_interleaved); + grid.set_output_size(tile_width * columns, tile_height * rows); + std::vector grid_data = grid.write(); + + auto file = ctx->get_heif_file(); + + // Encode Tiles + Error error; + std::vector tile_ids; + for (int i=0; i out_tile; + error = ctx->encode_image(tiles[i], + encoder, + options, + heif_image_input_class_normal, + out_tile); + heif_item_id tile_id = out_tile->get_id(); + file->get_infe_box(tile_id)->set_hidden_item(true); // only show the full grid + tile_ids.push_back(out_tile->get_id()); + } + + // Create Grid Item + heif_item_id grid_id = file->add_new_image(fourcc("grid")); + griditem = std::make_shared(ctx, grid_id); + ctx->insert_image_item(grid_id, griditem); + const int construction_method = 1; // 0=mdat 1=idat + file->append_iloc_data(grid_id, grid_data, construction_method); + + // Connect tiles to grid + file->add_iref_reference(grid_id, fourcc("dimg"), tile_ids); + + // Add ISPE property + uint32_t image_width = tile_width * columns; + uint32_t image_height = tile_height * rows; + file->add_ispe_property(grid_id, image_width, image_height, false); + + // Add PIXI property (copy from first tile) + auto pixi = file->get_property(tile_ids[0]); + file->add_property(grid_id, pixi, true); + + // Set Brands + file->set_brand(encoder->plugin->compression_format, + griditem->is_miaf_compatible()); + + return error; +} diff --git a/libheif/image-items/grid.h b/libheif/image-items/grid.h new file mode 100644 index 0000000000..22f23d4d7f --- /dev/null +++ b/libheif/image-items/grid.h @@ -0,0 +1,169 @@ +/* + * HEIF codec. + * Copyright (c) 2024 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_IMAGEITEM_GRID_H +#define LIBHEIF_IMAGEITEM_GRID_H + +#include "image_item.h" +#include +#include +#include + + +class ImageGrid +{ +public: + Error parse(const std::vector& data); + + std::vector write() const; + + std::string dump() const; + + uint32_t get_width() const { return m_output_width; } + + uint32_t get_height() const { return m_output_height; } + + uint16_t get_rows() const + { + return m_rows; + } + + uint16_t get_columns() const + { + return m_columns; + } + + void set_num_tiles(uint16_t columns, uint16_t rows) + { + m_rows = rows; + m_columns = columns; + } + + void set_output_size(uint32_t width, uint32_t height) + { + m_output_width = width; + m_output_height = height; + } + +private: + uint16_t m_rows = 0; + uint16_t m_columns = 0; + uint32_t m_output_width = 0; + uint32_t m_output_height = 0; +}; + + + + + +class ImageItem_Grid : public ImageItem +{ +public: + ImageItem_Grid(HeifContext* ctx, heif_item_id id); + + ImageItem_Grid(HeifContext* ctx); + + uint32_t get_infe_type() const override { return fourcc("grid"); } + + static Result> add_new_grid_item(HeifContext* ctx, + uint32_t output_width, + uint32_t output_height, + uint16_t tile_rows, + uint16_t tile_columns, + const struct heif_encoding_options* encoding_options); + + Error add_image_tile(heif_item_id grid_id, uint32_t tile_x, uint32_t tile_y, + const std::shared_ptr& image, + struct heif_encoder* encoder); + + static Result> add_and_encode_full_grid(HeifContext* ctx, + const std::vector>& tiles, + uint16_t rows, + uint16_t columns, + struct heif_encoder* encoder, + const struct heif_encoding_options& options); + + + // TODO: nclx depends on contained format + // const heif_color_profile_nclx* get_forced_output_nclx() const override { return nullptr; } + + // heif_compression_format get_compression_format() const override { return heif_compression_HEVC; } + + Error on_load_file() override; + + int get_luma_bits_per_pixel() const override; + + int get_chroma_bits_per_pixel() const override; + + void set_encoding_options(const heif_encoding_options* options) { + m_encoding_options = *options; + } + + const heif_encoding_options* get_encoding_options() const { return &m_encoding_options; } + + Result encode(const std::shared_ptr& image, + struct heif_encoder* encoder, + const struct heif_encoding_options& options, + enum heif_image_input_class input_class) override { + return Error{heif_error_Unsupported_feature, + heif_suberror_Unspecified, "Cannot encode image to 'grid'"}; + } + + Result> decode_compressed_image(const struct heif_decoding_options& options, + bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0) const override; + +protected: + std::shared_ptr get_decoder() const override; + +public: + + // --- grid specific + + const ImageGrid& get_grid_spec() const { return m_grid_spec; } + + void set_grid_spec(const ImageGrid& grid) { m_grid_spec = grid; m_grid_tile_ids.resize(grid.get_rows() * grid.get_columns()); } + + const std::vector& get_grid_tiles() const { return m_grid_tile_ids; } + + void set_grid_tile_id(uint32_t tile_x, uint32_t tile_y, heif_item_id); + + heif_image_tiling get_heif_image_tiling() const override; + + void get_tile_size(uint32_t& w, uint32_t& h) const override; + +private: + ImageGrid m_grid_spec; + std::vector m_grid_tile_ids; + + heif_encoding_options m_encoding_options; + + Error read_grid_spec(); + + Result> decode_full_grid_image(const heif_decoding_options& options) const; + + Result> decode_grid_tile(const heif_decoding_options& options, uint32_t tx, uint32_t ty) const; + + Error decode_and_paste_tile_image(heif_item_id tileID, uint32_t x0, uint32_t y0, + std::shared_ptr& inout_image, + const heif_decoding_options& options, int& progress_counter) const; +}; + + +#endif //LIBHEIF_GRID_H diff --git a/libheif/image-items/hevc.cc b/libheif/image-items/hevc.cc new file mode 100644 index 0000000000..9e8e15d3d6 --- /dev/null +++ b/libheif/image-items/hevc.cc @@ -0,0 +1,247 @@ +/* + * HEIF codec. + * Copyright (c) 2017 Dirk Farin + * + * This file is part of libheif. + * + * libheif is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * libheif is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libheif. If not, see . + */ + +#include "hevc.h" +#include "codecs/hevc_boxes.h" +#include "bitstream.h" +#include "error.h" +#include "file.h" +#include "codecs/hevc_dec.h" + +#include +#include +#include +#include +#include +#include +#include "api/libheif/api_structs.h" + + +Error ImageItem_HEVC::on_load_file() +{ + auto hvcC_box = get_file()->get_property(get_id()); + if (!hvcC_box) { + return Error{heif_error_Invalid_input, + heif_suberror_No_hvcC_box}; + } + + m_decoder = std::make_shared(hvcC_box); + + DataExtent extent; + extent.set_from_image_item(get_context()->get_heif_file(), get_id()); + + m_decoder->set_data_extent(extent); + + return Error::Ok; +} + + +Result ImageItem_HEVC::encode(const std::shared_ptr& image, + struct heif_encoder* encoder, + const struct heif_encoding_options& options, + enum heif_image_input_class input_class) +{ + CodedImageData codedImage; + + auto hvcC = std::make_shared(); + + heif_image c_api_image; + c_api_image.image = image; + + struct heif_error err = encoder->plugin->encode_image(encoder->encoder, &c_api_image, input_class); + if (err.code) { + return Error(err.code, + err.subcode, + err.message); + } + + int encoded_width = 0; + int encoded_height = 0; + + for (;;) { + uint8_t* data; + int size; + + encoder->plugin->get_compressed_data(encoder->encoder, &data, &size, nullptr); + + if (data == nullptr) { + break; + } + + + const uint8_t NAL_SPS = 33; + + if ((data[0] >> 1) == NAL_SPS) { + Box_hvcC::configuration config; + + parse_sps_for_hvcC_configuration(data, size, &config, &encoded_width, &encoded_height); + + hvcC->set_configuration(config); + + codedImage.encoded_image_width = encoded_width; + codedImage.encoded_image_height = encoded_height; + } + + switch (data[0] >> 1) { + case 0x20: + case 0x21: + case 0x22: + hvcC->append_nal_data(data, size); + break; + + default: + codedImage.append_with_4bytes_size(data, size); + // m_heif_file->append_iloc_data_with_4byte_size(image_id, data, size); + } + } + + if (!encoded_width || !encoded_height) { + return Error(heif_error_Encoder_plugin_error, + heif_suberror_Invalid_image_size); + } + + codedImage.properties.push_back(hvcC); + + + // Make sure that the encoder plugin works correctly and the encoded image has the correct size. + + if (encoder->plugin->plugin_api_version >= 3 && + encoder->plugin->query_encoded_size != nullptr) { + uint32_t check_encoded_width = image->get_width(), check_encoded_height = image->get_height(); + + encoder->plugin->query_encoded_size(encoder->encoder, + image->get_width(), image->get_height(), + &check_encoded_width, + &check_encoded_height); + + assert((int)check_encoded_width == encoded_width); + assert((int)check_encoded_height == encoded_height); + } + + return codedImage; +} + + +Result> ImageItem_HEVC::read_bitstream_configuration_data(heif_item_id itemId) const +{ + return m_decoder->read_bitstream_configuration_data(); +} + + +std::shared_ptr ImageItem_HEVC::get_decoder() const +{ + return m_decoder; +} + + +void ImageItem_HEVC::set_preencoded_hevc_image(const std::vector& data) +{ + auto hvcC = std::make_shared(); + + + // --- parse the h265 stream and set hvcC headers and compressed image data + + int state = 0; + + bool first = true; + bool eof = false; + + int prev_start_code_start = -1; // init to an invalid value, will always be overwritten before use + int start_code_start; + int ptr = 0; + + for (;;) { + bool dump_nal = false; + + uint8_t c = data[ptr++]; + + if (state == 3) { + state = 0; + } + + if (c == 0 && state <= 1) { + state++; + } + else if (c == 0) { + // NOP + } + else if (c == 1 && state == 2) { + start_code_start = ptr - 3; + dump_nal = true; + state = 3; + } + else { + state = 0; + } + + if (ptr == (int) data.size()) { + start_code_start = (int) data.size(); + dump_nal = true; + eof = true; + } + + if (dump_nal) { + if (first) { + first = false; + } + else { + std::vector nal_data; + size_t length = start_code_start - (prev_start_code_start + 3); + + nal_data.resize(length); + + assert(prev_start_code_start >= 0); + memcpy(nal_data.data(), data.data() + prev_start_code_start + 3, length); + + int nal_type = (nal_data[0] >> 1); + + switch (nal_type) { + case 0x20: + case 0x21: + case 0x22: + hvcC->append_nal_data(nal_data); + break; + + default: { + std::vector nal_data_with_size; + nal_data_with_size.resize(nal_data.size() + 4); + + memcpy(nal_data_with_size.data() + 4, nal_data.data(), nal_data.size()); + nal_data_with_size[0] = ((nal_data.size() >> 24) & 0xFF); + nal_data_with_size[1] = ((nal_data.size() >> 16) & 0xFF); + nal_data_with_size[2] = ((nal_data.size() >> 8) & 0xFF); + nal_data_with_size[3] = ((nal_data.size() >> 0) & 0xFF); + + get_file()->append_iloc_data(get_id(), nal_data_with_size, 0); + } + break; + } + } + + prev_start_code_start = start_code_start; + } + + if (eof) { + break; + } + } + + get_file()->add_property(get_id(), hvcC, true); +} diff --git a/libheif/image-items/hevc.h b/libheif/image-items/hevc.h new file mode 100644 index 0000000000..50bedfeea8 --- /dev/null +++ b/libheif/image-items/hevc.h @@ -0,0 +1,69 @@ +/* + * HEIF codec. + * Copyright (c) 2017 Dirk Farin + * + * This file is part of libheif. + * + * libheif is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as + * published by the Free Software Foundation, either version 3 of + * the License, or (at your option) any later version. + * + * libheif is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with libheif. If not, see . + */ + +#ifndef HEIF_HEVC_H +#define HEIF_HEVC_H + +#include "libheif/heif.h" +#include "box.h" +#include "error.h" + +#include +#include +#include +#include "image_item.h" + + +class ImageItem_HEVC : public ImageItem +{ +public: + ImageItem_HEVC(HeifContext* ctx, heif_item_id id) : ImageItem(ctx, id) {} + + ImageItem_HEVC(HeifContext* ctx) : ImageItem(ctx) {} + + uint32_t get_infe_type() const override { return fourcc("hvc1"); } + + // TODO: MIAF says that the *:hevc:* urn is deprecated and we should use "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha" + const char* get_auxC_alpha_channel_type() const override { return "urn:mpeg:hevc:2015:auxid:1"; } + + const heif_color_profile_nclx* get_forced_output_nclx() const override { return nullptr; } + + heif_compression_format get_compression_format() const override { return heif_compression_HEVC; } + + Error on_load_file() override; + + Result encode(const std::shared_ptr& image, + struct heif_encoder* encoder, + const struct heif_encoding_options& options, + enum heif_image_input_class input_class) override; + + // currently not used + void set_preencoded_hevc_image(const std::vector& data); + +protected: + Result> read_bitstream_configuration_data(heif_item_id itemId) const override; + + std::shared_ptr get_decoder() const override; + +private: + std::shared_ptr m_decoder; +}; + +#endif diff --git a/libheif/image-items/iden.cc b/libheif/image-items/iden.cc new file mode 100644 index 0000000000..41be785ad9 --- /dev/null +++ b/libheif/image-items/iden.cc @@ -0,0 +1,107 @@ +/* + * HEIF codec. + * Copyright (c) 2024 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 "iden.h" +#include "context.h" +#include "file.h" + + +ImageItem_iden::ImageItem_iden(HeifContext* ctx) + : ImageItem(ctx) +{ +} + + +ImageItem_iden::ImageItem_iden(HeifContext* ctx, heif_item_id id) + : ImageItem(ctx, id) +{ +} + + +Result> ImageItem_iden::decode_compressed_image(const struct heif_decoding_options& options, + bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0) const +{ + std::shared_ptr img; + + // find the ID of the image this image is derived from + + auto iref_box = get_file()->get_iref_box(); + + if (!iref_box) { + return Error(heif_error_Invalid_input, + heif_suberror_No_iref_box, + "No iref box available, but needed for iden image"); + } + + std::vector image_references = iref_box->get_references(get_id(), fourcc("dimg")); + + if ((int) image_references.size() != 1) { + return Error(heif_error_Invalid_input, + heif_suberror_Unspecified, + "'iden' image with more than one reference image"); + } + + + heif_item_id reference_image_id = image_references[0]; + + if (reference_image_id == get_id()) { + return Error(heif_error_Invalid_input, + heif_suberror_Unspecified, + "'iden' image referring to itself"); + } + + std::shared_ptr imgitem = get_context()->get_image(reference_image_id, true); + if (!imgitem) { + return Error(heif_error_Invalid_input, + heif_suberror_Unspecified, + "'iden' image references unavailable image"); + } + if (auto error = imgitem->get_item_error()) { + return error; + } + + return imgitem->decode_compressed_image(options, decode_tile_only, tile_x0, tile_y0); +} + + +int ImageItem_iden::get_luma_bits_per_pixel() const +{ + heif_item_id child; + Error err = get_context()->get_id_of_non_virtual_child_image(get_id(), child); + if (err) { + return -1; + } + + auto image = get_context()->get_image(child, true); + return image->get_luma_bits_per_pixel(); +} + + +int ImageItem_iden::get_chroma_bits_per_pixel() const +{ + heif_item_id child; + Error err = get_context()->get_id_of_non_virtual_child_image(get_id(), child); + if (err) { + return -1; + } + + auto image = get_context()->get_image(child, true); + return image->get_chroma_bits_per_pixel(); +} diff --git a/libheif/image-items/iden.h b/libheif/image-items/iden.h new file mode 100644 index 0000000000..25403cd2a6 --- /dev/null +++ b/libheif/image-items/iden.h @@ -0,0 +1,66 @@ +/* + * HEIF codec. + * Copyright (c) 2024 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_IDEN_H +#define LIBHEIF_IDEN_H + +#include "image_item.h" +#include +#include +#include + + +class ImageItem_iden : public ImageItem +{ +public: + ImageItem_iden(HeifContext* ctx, heif_item_id id); + + ImageItem_iden(HeifContext* ctx); + + uint32_t get_infe_type() const override { return fourcc("iden"); } + + // TODO: nclx depends on contained format + // const heif_color_profile_nclx* get_forced_output_nclx() const override { return nullptr; } + + // heif_compression_format get_compression_format() const override { return heif_compression_HEVC; } + + //Error on_load_file() override; + + int get_luma_bits_per_pixel() const override; + + int get_chroma_bits_per_pixel() const override; + + Result encode(const std::shared_ptr& image, + struct heif_encoder* encoder, + const struct heif_encoding_options& options, + enum heif_image_input_class input_class) override + { + return Error{heif_error_Unsupported_feature, + heif_suberror_Unspecified, "Cannot encode image to 'iden'"}; + } + + Result> decode_compressed_image(const struct heif_decoding_options& options, + bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0) const override; + +private: +}; + + +#endif //LIBHEIF_IDEN_H diff --git a/libheif/image-items/image_item.cc b/libheif/image-items/image_item.cc new file mode 100644 index 0000000000..f37db1fcdb --- /dev/null +++ b/libheif/image-items/image_item.cc @@ -0,0 +1,1146 @@ +/* + * HEIF image base codec. + * Copyright (c) 2024 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 "image_item.h" +#include "mask_image.h" +#include "context.h" +#include "file.h" +#include "jpeg.h" +#include "jpeg2000.h" +#include "avif.h" +#include "avc.h" +#include "hevc.h" +#include "grid.h" +#include "overlay.h" +#include "iden.h" +#include "tiled.h" +#include "codecs/decoder.h" +#include "color-conversion/colorconversion.h" +#include "api/libheif/api_structs.h" +#include "plugin_registry.h" +#include "security_limits.h" + +#include +#include +#include +//#include + +#if WITH_UNCOMPRESSED_CODEC +#include "image-items/unc_image.h" +#endif + + +ImageItem::ImageItem(HeifContext* context) + : m_heif_context(context) +{ + memset(&m_depth_representation_info, 0, sizeof(m_depth_representation_info)); +} + + +ImageItem::ImageItem(HeifContext* context, heif_item_id id) + : ImageItem(context) +{ + m_id = id; +} + + +std::shared_ptr ImageItem::get_file() const +{ + return m_heif_context->get_heif_file(); +} + + +Error ImageItem::init_decoder_from_item(heif_item_id id) +{ + m_id = id; + + Error err = on_load_file(); + return err; +} + + +heif_compression_format ImageItem::compression_format_from_fourcc_infe_type(uint32_t type) +{ + switch (type) { + case fourcc("jpeg"): + return heif_compression_JPEG; + case fourcc("hvc1"): + return heif_compression_HEVC; + case fourcc("av01"): + return heif_compression_AV1; + case fourcc("vvc1"): + return heif_compression_VVC; + case fourcc("j2k1"): + return heif_compression_JPEG2000; + case fourcc("unci"): + return heif_compression_uncompressed; + case fourcc("mski"): + return heif_compression_mask; + default: + return heif_compression_undefined; + } +} + +uint32_t ImageItem::compression_format_to_fourcc_infe_type(heif_compression_format format) +{ + switch (format) { + case heif_compression_JPEG: + return fourcc("jpeg"); + case heif_compression_HEVC: + return fourcc("hvc1"); + case heif_compression_AV1: + return fourcc("av01"); + case heif_compression_VVC: + return fourcc("vvc1"); + case heif_compression_JPEG2000: + return fourcc("j2k1"); + case heif_compression_uncompressed: + return fourcc("unci"); + case heif_compression_mask: + return fourcc("mski"); + default: + return 0; + } +} + + +std::shared_ptr ImageItem::alloc_for_infe_box(HeifContext* ctx, const std::shared_ptr& infe) +{ + uint32_t item_type = infe->get_item_type_4cc(); + heif_item_id id = infe->get_item_ID(); + + if (item_type == fourcc("jpeg") || + (item_type == fourcc("mime") && infe->get_content_type() == "image/jpeg")) { + return std::make_shared(ctx, id); + } + else if (item_type == fourcc("hvc1")) { + return std::make_shared(ctx, id); + } + else if (item_type == fourcc("av01")) { + return std::make_shared(ctx, id); + } + else if (item_type == fourcc("vvc1")) { + return std::make_shared(ctx, id); + } + else if (item_type == fourcc("avc1")) { + return std::make_shared(ctx, id); + } + else if (item_type == fourcc("unci")) { +#if WITH_UNCOMPRESSED_CODEC + return std::make_shared(ctx, id); +#else + // It is an image item type that we do not support. Thus, generate an ImageItem_Error. + + std::stringstream sstr; + sstr << "Image item of type '" << fourcc_to_string(item_type) << "' is not supported."; + Error err{ heif_error_Unsupported_feature, heif_suberror_Unsupported_image_type, sstr.str() }; + return std::make_shared(item_type, id, err); +#endif + } + else if (item_type == fourcc("j2k1")) { + return std::make_shared(ctx, id); + } + else if (item_type == fourcc("mski")) { + return std::make_shared(ctx, id); + } + else if (item_type == fourcc("grid")) { + return std::make_shared(ctx, id); + } + else if (item_type == fourcc("iovl")) { + return std::make_shared(ctx, id); + } + else if (item_type == fourcc("iden")) { + return std::make_shared(ctx, id); + } + else if (item_type == fourcc("tili")) { + return std::make_shared(ctx, id); + } + else { + // This item has an unknown type. It could be an image or anything else. + // Do not process the item. + + return nullptr; + } +} + + +std::shared_ptr ImageItem::alloc_for_compression_format(HeifContext* ctx, heif_compression_format format) +{ + switch (format) { + case heif_compression_JPEG: + return std::make_shared(ctx); + case heif_compression_HEVC: + return std::make_shared(ctx); + case heif_compression_AV1: + return std::make_shared(ctx); + case heif_compression_VVC: + return std::make_shared(ctx); +#if WITH_UNCOMPRESSED_CODEC + case heif_compression_uncompressed: + return std::make_shared(ctx); +#endif + case heif_compression_JPEG2000: + case heif_compression_HTJ2K: + return std::make_shared(ctx); + case heif_compression_mask: + return std::make_shared(ctx); + default: + assert(false); + return nullptr; + } +} + + +Result ImageItem::encode_to_bitstream_and_boxes(const std::shared_ptr& image, + struct heif_encoder* encoder, + const struct heif_encoding_options& options, + enum heif_image_input_class input_class) +{ + // === generate compressed image bitstream + + Result encodeResult = encode(image, encoder, options, input_class); + if (encodeResult.error) { + return encodeResult; + } + + CodedImageData& codedImage = encodeResult.value; + + // === generate properties + + // --- choose which color profile to put into 'colr' box + + add_color_profile(image, options, input_class, options.output_nclx_profile, codedImage); + + + // --- ispe + // Note: 'ispe' must come before the transformation properties + + uint32_t input_width, input_height; + input_width = image->get_width(); + input_height = image->get_height(); + + // --- get the real size of the encoded image + + // highest priority: codedImageData + uint32_t encoded_width = codedImage.encoded_image_width; + uint32_t encoded_height = codedImage.encoded_image_height; + + // second priority: query plugin API + if (encoded_width == 0 && + encoder->plugin->plugin_api_version >= 3 && + encoder->plugin->query_encoded_size != nullptr) { + + encoder->plugin->query_encoded_size(encoder->encoder, + input_width, input_height, + &encoded_width, + &encoded_height); + } + else if (encoded_width == 0) { + // fallback priority: use input size + encoded_width = input_width; + encoded_height = input_height; + } + + auto ispe = std::make_shared(); + ispe->set_size(encoded_width, encoded_height); + ispe->set_is_essential(is_ispe_essential()); + codedImage.properties.push_back(ispe); + + + // --- clap (if needed) + + if (input_width != encoded_width || + input_height != encoded_height) { + + auto clap = std::make_shared(); + clap->set(input_width, input_height, encoded_width, encoded_height); + codedImage.properties.push_back(clap); + } + + + + // --- add common metadata properties (pixi, ...) + + auto colorspace = image->get_colorspace(); + auto chroma = image->get_chroma_format(); + + + // --- write PIXI property + + std::shared_ptr pixi = std::make_shared(); + if (colorspace == heif_colorspace_monochrome) { + pixi->add_channel_bits(image->get_bits_per_pixel(heif_channel_Y)); + } + else if (colorspace == heif_colorspace_YCbCr) { + pixi->add_channel_bits(image->get_bits_per_pixel(heif_channel_Y)); + pixi->add_channel_bits(image->get_bits_per_pixel(heif_channel_Cb)); + pixi->add_channel_bits(image->get_bits_per_pixel(heif_channel_Cr)); + } + else if (colorspace == heif_colorspace_RGB) { + if (chroma == heif_chroma_444) { + pixi->add_channel_bits(image->get_bits_per_pixel(heif_channel_R)); + pixi->add_channel_bits(image->get_bits_per_pixel(heif_channel_G)); + pixi->add_channel_bits(image->get_bits_per_pixel(heif_channel_B)); + } + else if (chroma == heif_chroma_interleaved_RGB || + chroma == heif_chroma_interleaved_RGBA || + chroma == heif_chroma_interleaved_RRGGBB_LE || + chroma == heif_chroma_interleaved_RRGGBB_BE || + chroma == heif_chroma_interleaved_RRGGBBAA_LE || + chroma == heif_chroma_interleaved_RRGGBBAA_BE) { + uint8_t bpp = image->get_bits_per_pixel(heif_channel_interleaved); + pixi->add_channel_bits(bpp); + pixi->add_channel_bits(bpp); + pixi->add_channel_bits(bpp); + } + } + codedImage.properties.push_back(pixi); + + + // --- write PASP property + + if (image->has_nonsquare_pixel_ratio()) { + auto pasp = std::make_shared(); + image->get_pixel_ratio(&pasp->hSpacing, &pasp->vSpacing); + + codedImage.properties.push_back(pasp); + } + + + // --- write CLLI property + + if (image->has_clli()) { + auto clli = std::make_shared(); + clli->clli = image->get_clli(); + + codedImage.properties.push_back(clli); + } + + + // --- write MDCV property + + if (image->has_mdcv()) { + auto mdcv = std::make_shared(); + mdcv->mdcv = image->get_mdcv(); + + codedImage.properties.push_back(mdcv); + } + + return encodeResult; +} + + +Error ImageItem::encode_to_item(HeifContext* ctx, + const std::shared_ptr& image, + struct heif_encoder* encoder, + const struct heif_encoding_options& options, + enum heif_image_input_class input_class) +{ + uint32_t input_width = image->get_width(); + uint32_t input_height = image->get_height(); + + set_size(input_width, input_height); + + + // compress image and assign data to item + + Result codingResult = encode_to_bitstream_and_boxes(image, encoder, options, input_class); + if (codingResult.error) { + return codingResult.error; + } + + CodedImageData& codedImage = codingResult.value; + + auto infe_box = ctx->get_heif_file()->add_new_infe_box(get_infe_type()); + heif_item_id image_id = infe_box->get_item_ID(); + set_id(image_id); + + ctx->get_heif_file()->append_iloc_data(image_id, codedImage.bitstream, 0); + + + // set item properties + + for (auto& propertyBox : codingResult.value.properties) { + int index = ctx->get_heif_file()->get_ipco_box()->find_or_append_child_box(propertyBox); + ctx->get_heif_file()->get_ipma_box()->add_property_for_item_ID(image_id, Box_ipma::PropertyAssociation{propertyBox->is_essential(), + uint16_t(index + 1)}); + } + + + // MIAF 7.3.6.7 + // This is according to MIAF without Amd2. With Amd2, the restriction has been lifted and the image is MIAF compatible. + // However, since AVIF is based on MIAF, the whole image would be invalid in that case. + + // We might remove this code at a later point in time when MIAF Amd2 is in wide use. + + if (encoder->plugin->compression_format != heif_compression_AV1 && + image->get_colorspace() == heif_colorspace_YCbCr) { + if (!is_integer_multiple_of_chroma_size(image->get_width(), + image->get_height(), + image->get_chroma_format())) { + mark_not_miaf_compatible(); + } + } + + // TODO: move this into encode_to_bistream_and_boxes() + ctx->get_heif_file()->add_orientation_properties(image_id, options.image_orientation); + + return Error::Ok; +} + + +uint32_t ImageItem::get_ispe_width() const +{ + auto ispe = m_heif_context->get_heif_file()->get_property(m_id); + if (!ispe) { + return 0; + } + else { + return ispe->get_width(); + } +} + + +uint32_t ImageItem::get_ispe_height() const +{ + auto ispe = m_heif_context->get_heif_file()->get_property(m_id); + if (!ispe) { + return 0; + } + else { + return ispe->get_height(); + } +} + + +void ImageItem::get_tile_size(uint32_t& w, uint32_t& h) const +{ + w = get_width(); + h = get_height(); +} + + +Error ImageItem::postprocess_coded_image_colorspace(heif_colorspace* inout_colorspace, heif_chroma* inout_chroma) const +{ +#if 0 + auto pixi = m_heif_context->get_heif_file()->get_property(id); + if (pixi && pixi->get_num_channels() == 1) { + *out_colorspace = heif_colorspace_monochrome; + *out_chroma = heif_chroma_monochrome; + } +#endif + + if (*inout_colorspace == heif_colorspace_YCbCr) { + auto nclx = get_color_profile_nclx(); + if (nclx && nclx->get_matrix_coefficients() == 0) { + *inout_colorspace = heif_colorspace_RGB; + *inout_chroma = heif_chroma_444; // TODO: this or keep the original chroma? + } + } + + return Error::Ok; +} + + +Error ImageItem::get_coded_image_colorspace(heif_colorspace* out_colorspace, heif_chroma* out_chroma) const +{ + auto decoder = get_decoder(); + assert(decoder); + + Error err = decoder->get_coded_image_colorspace(out_colorspace, out_chroma); + if (err) { + return err; + } + + postprocess_coded_image_colorspace(out_colorspace, out_chroma); + + return Error::Ok; +} + + +int ImageItem::get_luma_bits_per_pixel() const +{ + auto decoder = get_decoder(); + assert(decoder); + + return decoder->get_luma_bits_per_pixel(); +} + + +int ImageItem::get_chroma_bits_per_pixel() const +{ + auto decoder = get_decoder(); + assert(decoder); + + return decoder->get_chroma_bits_per_pixel(); +} + + +static std::shared_ptr compute_target_nclx_profile(const std::shared_ptr& image, const heif_color_profile_nclx* output_nclx_profile) +{ + auto target_nclx_profile = std::make_shared(); + + // If there is an output NCLX specified, use that. + if (output_nclx_profile) { + target_nclx_profile->set_from_heif_color_profile_nclx(output_nclx_profile); + } + // Otherwise, if there is an input NCLX, keep that. + else if (auto input_nclx = image->get_color_profile_nclx()) { + *target_nclx_profile = *input_nclx; + } + // Otherwise, just use the defaults (set below) + else { + target_nclx_profile->set_undefined(); + } + + target_nclx_profile->replace_undefined_values_with_sRGB_defaults(); + + return target_nclx_profile; +} + + +static bool nclx_profile_matches_spec(heif_colorspace colorspace, + std::shared_ptr image_nclx, + const struct heif_color_profile_nclx* spec_nclx) +{ + if (colorspace != heif_colorspace_YCbCr) { + return true; + } + + // No target specification -> always matches + if (!spec_nclx) { + return true; + } + + if (!image_nclx) { + // if no input nclx is specified, compare against default one + image_nclx = std::make_shared(); + } + + if (image_nclx->get_full_range_flag() != (spec_nclx->full_range_flag == 0 ? false : true)) { + return false; + } + + if (image_nclx->get_matrix_coefficients() != spec_nclx->matrix_coefficients) { + return false; + } + + // TODO: are the colour primaries relevant for matrix-coefficients != 12,13 ? + // If not, we should skip this test for anything else than matrix-coefficients != 12,13. + if (image_nclx->get_colour_primaries() != spec_nclx->color_primaries) { + return false; + } + + return true; +} + + +Result> ImageItem::convert_colorspace_for_encoding(const std::shared_ptr& image, + struct heif_encoder* encoder, + const struct heif_encoding_options& options) +{ + const heif_color_profile_nclx* output_nclx_profile; + + if (const auto* nclx = get_forced_output_nclx()) { + output_nclx_profile = nclx; + } + else { + output_nclx_profile = options.output_nclx_profile; + } + + + heif_colorspace colorspace = image->get_colorspace(); + heif_chroma chroma = image->get_chroma_format(); + + if (encoder->plugin->plugin_api_version >= 2) { + encoder->plugin->query_input_colorspace2(encoder->encoder, &colorspace, &chroma); + } + else { + encoder->plugin->query_input_colorspace(&colorspace, &chroma); + } + + + // If output format forces an NCLX, use that. Otherwise use user selected NCLX. + + std::shared_ptr target_nclx_profile = compute_target_nclx_profile(image, output_nclx_profile); + + // --- convert colorspace + + std::shared_ptr output_image; + + if (colorspace != image->get_colorspace() || + chroma != image->get_chroma_format() || + !nclx_profile_matches_spec(colorspace, image->get_color_profile_nclx(), output_nclx_profile)) { + // @TODO: use color profile when converting + int output_bpp = 0; // same as input + + //auto target_nclx = std::make_shared(); + //target_nclx->set_from_heif_color_profile_nclx(target_heif_nclx); + + output_image = convert_colorspace(image, colorspace, chroma, target_nclx_profile, + output_bpp, options.color_conversion_options); + if (!output_image) { + return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_color_conversion); + } + } + else { + output_image = image; + } + + return output_image; +} + + +void ImageItem::add_color_profile(const std::shared_ptr& image, + const struct heif_encoding_options& options, + enum heif_image_input_class input_class, + const heif_color_profile_nclx* target_heif_nclx, + ImageItem::CodedImageData& inout_codedImage) +{ + if (input_class == heif_image_input_class_normal || input_class == heif_image_input_class_thumbnail) { + auto icc_profile = image->get_color_profile_icc(); + if (icc_profile) { + auto colr = std::make_shared(); + colr->set_color_profile(icc_profile); + inout_codedImage.properties.push_back(colr); + } + + + // save nclx profile + + bool save_nclx_profile = (options.output_nclx_profile != nullptr); + + // if there is an ICC profile, only save NCLX when we chose to save both profiles + if (icc_profile && !(options.version >= 3 && + options.save_two_colr_boxes_when_ICC_and_nclx_available)) { + save_nclx_profile = false; + } + + // we might have turned off nclx completely because macOS/iOS cannot read it + if (options.version >= 4 && options.macOS_compatibility_workaround_no_nclx_profile) { + save_nclx_profile = false; + } + + if (save_nclx_profile) { + auto target_nclx_profile = std::make_shared(); + target_nclx_profile->set_from_heif_color_profile_nclx(target_heif_nclx); + + auto colr = std::make_shared(); + colr->set_color_profile(target_nclx_profile); + inout_codedImage.properties.push_back(colr); + } + } +} + + +void ImageItem::CodedImageData::append(const uint8_t* data, size_t size) +{ + bitstream.insert(bitstream.end(), data, data + size); +} + + +void ImageItem::CodedImageData::append_with_4bytes_size(const uint8_t* data, size_t size) +{ + assert(size <= 0xFFFFFFFF); + + uint8_t size_field[4]; + size_field[0] = (uint8_t) ((size >> 24) & 0xFF); + size_field[1] = (uint8_t) ((size >> 16) & 0xFF); + size_field[2] = (uint8_t) ((size >> 8) & 0xFF); + size_field[3] = (uint8_t) ((size >> 0) & 0xFF); + + bitstream.insert(bitstream.end(), size_field, size_field + 4); + bitstream.insert(bitstream.end(), data, data + size); +} + + +Error ImageItem::transform_requested_tile_position_to_original_tile_position(uint32_t& tile_x, uint32_t& tile_y) const +{ + Result>> propertiesResult = get_properties(); + if (propertiesResult.error) { + return propertiesResult.error; + } + + heif_image_tiling tiling = get_heif_image_tiling(); + + //for (auto& prop : std::ranges::reverse_view(propertiesResult.value)) { + for (auto propIter = propertiesResult.value.rbegin(); propIter != propertiesResult.value.rend(); propIter++) { + if (auto irot = std::dynamic_pointer_cast(*propIter)) { + switch (irot->get_rotation_ccw()) { + case 90: { + uint32_t tx0 = tiling.num_columns - 1 - tile_y; + uint32_t ty0 = tile_x; + tile_y = ty0; + tile_x = tx0; + break; + } + case 270: { + uint32_t tx0 = tile_y; + uint32_t ty0 = tiling.num_rows - 1 - tile_x; + tile_y = ty0; + tile_x = tx0; + break; + } + case 180: { + tile_x = tiling.num_columns - 1 - tile_x; + tile_y = tiling.num_rows - 1 - tile_y; + break; + } + case 0: + break; + default: + assert(false); + break; + } + } + + if (auto imir = std::dynamic_pointer_cast(*propIter)) { + switch (imir->get_mirror_direction()) { + case heif_transform_mirror_direction_horizontal: + tile_x = tiling.num_columns - 1 - tile_x; + break; + case heif_transform_mirror_direction_vertical: + tile_y = tiling.num_rows - 1 - tile_y; + break; + default: + assert(false); + break; + } + } + } + + return Error::Ok; +} + + +Result> ImageItem::decode_image(const struct heif_decoding_options& options, + bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0) const +{ + // --- check whether image size (according to 'ispe') exceeds maximum + + if (!decode_tile_only) { + auto ispe = m_heif_context->get_heif_file()->get_property(m_id); + if (ispe) { + Error err = check_for_valid_image_size(get_context()->get_security_limits(), ispe->get_width(), ispe->get_height()); + if (err) { + return err; + } + } + } + + + // --- transform tile position + + if (decode_tile_only && options.ignore_transformations == false) { + if (Error error = transform_requested_tile_position_to_original_tile_position(tile_x0, tile_y0)) { + return error; + } + } + + // --- decode image + + Result> decodingResult = decode_compressed_image(options, decode_tile_only, tile_x0, tile_y0); + if (decodingResult.error) { + return decodingResult.error; + } + + auto img = decodingResult.value; + + std::shared_ptr file = m_heif_context->get_heif_file(); + + + // --- apply image transformations + + Error error; + + if (options.ignore_transformations == false) { + Result>> propertiesResult = get_properties(); + if (propertiesResult.error) { + return propertiesResult.error; + } + + const std::vector>& properties = *propertiesResult; + + for (const auto& property : properties) { + if (auto rot = std::dynamic_pointer_cast(property)) { + auto rotateResult = img->rotate_ccw(rot->get_rotation_ccw()); + if (rotateResult.error) { + return error; + } + + img = rotateResult.value; + } + + + if (auto mirror = std::dynamic_pointer_cast(property)) { + auto mirrorResult = img->mirror_inplace(mirror->get_mirror_direction()); + if (mirrorResult.error) { + return error; + } + img = mirrorResult.value; + } + + + if (!decode_tile_only) { + // For tiles decoding, we do not process the 'clap' because this is handled by a shift of the tiling grid. + + if (auto clap = std::dynamic_pointer_cast(property)) { + std::shared_ptr clap_img; + + uint32_t img_width = img->get_width(); + uint32_t img_height = img->get_height(); + assert(img_width >= 0); + assert(img_height >= 0); + + int left = clap->left_rounded(img_width); + int right = clap->right_rounded(img_width); + int top = clap->top_rounded(img_height); + int bottom = clap->bottom_rounded(img_height); + + if (left < 0) { left = 0; } + if (top < 0) { top = 0; } + + if ((uint32_t) right >= img_width) { right = img_width - 1; } + if ((uint32_t) bottom >= img_height) { bottom = img_height - 1; } + + if (left > right || + top > bottom) { + return Error(heif_error_Invalid_input, + heif_suberror_Invalid_clean_aperture); + } + + auto cropResult = img->crop(left, right, top, bottom); + if (error) { + return error; + } + + img = cropResult.value; + } + } + } + } + + + // --- add alpha channel, if available + + // TODO: this if statement is probably wrong. When we have a tiled image with alpha + // channel, then the alpha images should be associated with their respective tiles. + // However, the tile images are not part of the m_all_images list. + // Fix this, when we have a test image available. + + std::shared_ptr alpha_image = get_alpha_channel(); + if (alpha_image) { + auto alphaDecodingResult = alpha_image->decode_image(options, decode_tile_only, tile_x0, tile_y0); + if (alphaDecodingResult.error) { + return alphaDecodingResult.error; + } + + std::shared_ptr alpha = alphaDecodingResult.value; + + // TODO: check that sizes are the same and that we have an Y channel + // BUT: is there any indication in the standard that the alpha channel should have the same size? + + // TODO: convert in case alpha is decoded as RGB interleaved + + heif_channel channel; + switch (alpha->get_colorspace()) { + case heif_colorspace_YCbCr: + case heif_colorspace_monochrome: + channel = heif_channel_Y; + break; + case heif_colorspace_RGB: + channel = heif_channel_R; + break; + case heif_colorspace_undefined: + default: + return Error(heif_error_Invalid_input, + heif_suberror_Unsupported_color_conversion); + } + + + // TODO: we should include a decoding option to control whether libheif should automatically scale the alpha channel, and if so, which scaling filter (enum: Off, NN, Bilinear, ...). + // It might also be that a specific output format implies that alpha is scaled (RGBA32). That would favor an enum for the scaling filter option + a bool to switch auto-filtering on. + // But we can only do this when libheif itself doesn't assume anymore that the alpha channel has the same resolution. + + if ((alpha_image->get_width() != img->get_width()) || (alpha_image->get_height() != img->get_height())) { + std::shared_ptr scaled_alpha; + Error err = alpha->scale_nearest_neighbor(scaled_alpha, img->get_width(), img->get_height()); + if (err) { + return err; + } + alpha = std::move(scaled_alpha); + } + img->transfer_plane_from_image_as(alpha, channel, heif_channel_Alpha); + + if (is_premultiplied_alpha()) { + img->set_premultiplied_alpha(true); + } + } + + + // --- set color profile + + // If there is an NCLX profile in the HEIF/AVIF metadata, use this for the color conversion. + // Otherwise, use the profile that is stored in the image stream itself and then set the + // (non-NCLX) profile later. + auto nclx = get_color_profile_nclx(); + if (nclx) { + img->set_color_profile_nclx(nclx); + } + + auto icc = get_color_profile_icc(); + if (icc) { + img->set_color_profile_icc(icc); + } + + + // --- attach metadata to image + + { + auto ipco_box = file->get_ipco_box(); + auto ipma_box = file->get_ipma_box(); + + // CLLI + + auto clli = get_file()->get_property(m_id); + if (clli) { + img->set_clli(clli->clli); + } + + // MDCV + + auto mdcv = get_file()->get_property(m_id); + if (mdcv) { + img->set_mdcv(mdcv->mdcv); + } + + // PASP + + auto pasp = get_file()->get_property(m_id); + if (pasp) { + img->set_pixel_ratio(pasp->hSpacing, pasp->vSpacing); + } + } + + return img; +} + + +Result> ImageItem::read_bitstream_configuration_data_override(heif_item_id itemId, heif_compression_format format) const +{ + auto item_codec = ImageItem::alloc_for_compression_format(const_cast(get_context()), format); + assert(item_codec); + + Error err = item_codec->init_decoder_from_item(itemId); + if (err) { + return err; + } + + return item_codec->read_bitstream_configuration_data(itemId); +} + + +Result> ImageItem::get_compressed_image_data() const +{ + // TODO: Remove this later when decoding is done through Decoder. + + // --- get the compressed image data + + // data from configuration blocks + + Result> confData = read_bitstream_configuration_data(get_id()); + if (confData.error) { + return confData.error; + } + + std::vector data = confData.value; + + // image data, usually from 'mdat' + + Error error = m_heif_context->get_heif_file()->append_data_from_iloc(m_id, data); + if (error) { + return error; + } + + return data; +} + + +Result> ImageItem::decode_compressed_image(const struct heif_decoding_options& options, + bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0) const +{ + DataExtent extent; + extent.set_from_image_item(get_file(), get_id()); + + auto decoder = get_decoder(); + assert(decoder); + + decoder->set_data_extent(std::move(extent)); + + return decoder->decode_single_frame_from_compressed_data(options); +} + + +heif_image_tiling ImageItem::get_heif_image_tiling() const +{ + // --- Return a dummy tiling consisting of only a single tile for the whole image + + heif_image_tiling tiling{}; + + tiling.version = 1; + tiling.num_columns = 1; + tiling.num_rows = 1; + + tiling.tile_width = m_width; + tiling.tile_height = m_height; + tiling.image_width = m_width; + tiling.image_height = m_height; + + tiling.top_offset = 0; + tiling.left_offset = 0; + tiling.number_of_extra_dimensions = 0; + + for (uint32_t& s : tiling.extra_dimension_size) { + s = 0; + } + + return tiling; +} + + +Result>> ImageItem::get_properties() const +{ + std::vector> properties; + auto ipco_box = get_file()->get_ipco_box(); + auto ipma_box = get_file()->get_ipma_box(); + Error error = ipco_box->get_properties_for_item_ID(m_id, ipma_box, properties); + if (error) { + return error; + } + + return properties; +} + + +Error ImageItem::process_image_transformations_on_tiling(heif_image_tiling& tiling) const +{ + Result>> propertiesResult = get_properties(); + if (propertiesResult.error) { + return propertiesResult.error; + } + + const std::vector>& properties = *propertiesResult; + + uint32_t left_excess = 0; + uint32_t top_excess = 0; + uint32_t right_excess = tiling.image_width % tiling.tile_width; + uint32_t bottom_excess = tiling.image_height % tiling.tile_height; + + for (const auto& property : properties) { + + // --- rotation + + if (auto rot = std::dynamic_pointer_cast(property)) { + int angle = rot->get_rotation_ccw(); + if (angle == 90 || angle == 270) { + std::swap(tiling.tile_width, tiling.tile_height); + std::swap(tiling.image_width, tiling.image_height); + std::swap(tiling.num_rows, tiling.num_columns); + } + + switch (angle) { + case 0: + break; + case 180: + std::swap(left_excess, right_excess); + std::swap(top_excess, bottom_excess); + break; + case 90: { + uint32_t old_top_excess = top_excess; + top_excess = right_excess; + right_excess = bottom_excess; + bottom_excess = left_excess; + left_excess = old_top_excess; + break; + } + case 270: { + uint32_t old_top_excess = top_excess; + top_excess = left_excess; + left_excess = bottom_excess; + bottom_excess = right_excess; + right_excess = old_top_excess; + break; + } + default: + assert(false); + break; + } + } + + // --- mirror + + if (auto mirror = std::dynamic_pointer_cast(property)) { + switch (mirror->get_mirror_direction()) { + case heif_transform_mirror_direction_horizontal: + std::swap(left_excess, right_excess); + break; + case heif_transform_mirror_direction_vertical: + std::swap(top_excess, bottom_excess); + break; + default: + assert(false); + break; + } + } + + // --- crop + + if (auto clap = std::dynamic_pointer_cast(property)) { + std::shared_ptr clap_img; + + int left = clap->left_rounded(tiling.image_width); + int right = clap->right_rounded(tiling.image_width); + int top = clap->top_rounded(tiling.image_height); + int bottom = clap->bottom_rounded(tiling.image_height); + + if (left < 0) { left = 0; } + if (top < 0) { top = 0; } + + if ((uint32_t)right >= tiling.image_width) { right = tiling.image_width - 1; } + if ((uint32_t)bottom >= tiling.image_height) { bottom = tiling.image_height - 1; } + + if (left > right || + top > bottom) { + return {heif_error_Invalid_input, + heif_suberror_Invalid_clean_aperture}; + } + + left_excess += left; + right_excess += right; + top_excess += top; + bottom_excess += bottom; + } + } + + tiling.left_offset = left_excess; + tiling.top_offset = top_excess; + + return Error::Ok; +} diff --git a/libheif/image-items/image_item.h b/libheif/image-items/image_item.h new file mode 100644 index 0000000000..8149b58c61 --- /dev/null +++ b/libheif/image-items/image_item.h @@ -0,0 +1,446 @@ +/* + * HEIF image base codec. + * Copyright (c) 2024 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_IMAGEITEM_H +#define LIBHEIF_IMAGEITEM_H + +#include "api/libheif/heif.h" +#include "error.h" +#include "nclx.h" +#include +#include +#include +#include +#include "api/libheif/heif_plugin.h" + +class HeifContext; + +class HeifPixelImage; + + +class ImageMetadata +{ +public: + heif_item_id item_id; + std::string item_type; // e.g. "Exif" + std::string content_type; + std::string item_uri_type; + std::vector m_data; +}; + + +class ImageItem : public ErrorBuffer +{ +public: + ImageItem(HeifContext* file); + + ImageItem(HeifContext* file, heif_item_id id); + + virtual ~ImageItem() = default; + + static std::shared_ptr alloc_for_infe_box(HeifContext*, const std::shared_ptr&); + + static std::shared_ptr alloc_for_compression_format(HeifContext*, heif_compression_format); + + static heif_compression_format compression_format_from_fourcc_infe_type(uint32_t type); + + static uint32_t compression_format_to_fourcc_infe_type(heif_compression_format); + + Result> convert_colorspace_for_encoding(const std::shared_ptr& image, + struct heif_encoder* encoder, + const struct heif_encoding_options& options); + + virtual uint32_t get_infe_type() const { return 0; } + + virtual const char* get_auxC_alpha_channel_type() const { return "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"; } + + virtual bool is_ispe_essential() const { return false; } + + virtual Error get_item_error() const { return Error::Ok; } + + // If the output format requires a specific nclx (like JPEG), return this. Otherwise, return NULL. + virtual const heif_color_profile_nclx* get_forced_output_nclx() const { return nullptr; } + + virtual heif_compression_format get_compression_format() const { return heif_compression_undefined; } + + virtual Result> read_bitstream_configuration_data(heif_item_id itemId) const { return std::vector{}; } + + void clear() + { + m_thumbnails.clear(); + m_alpha_channel.reset(); + m_depth_channel.reset(); + m_aux_images.clear(); + } + + HeifContext* get_context() { return m_heif_context; } + + const HeifContext* get_context() const { return m_heif_context; } + + std::shared_ptr get_file() const; + + void set_resolution(uint32_t w, uint32_t h) + { + m_width = w; + m_height = h; + } + + heif_item_id get_id() const { return m_id; } + + void set_id(heif_item_id id) { m_id = id; } + + void set_primary(bool flag = true) { m_is_primary = flag; } + + bool is_primary() const { return m_is_primary; } + + // 32bit limitation from `ispe` + uint32_t get_width() const { return m_width; } + + uint32_t get_height() const { return m_height; } + + uint32_t get_ispe_width() const; + + uint32_t get_ispe_height() const; + + // Default behavior: forward call to Decoder + [[nodiscard]] virtual int get_luma_bits_per_pixel() const; + + // Default behavior: forward call to Decoder + [[nodiscard]] virtual int get_chroma_bits_per_pixel() const; + + void set_size(uint32_t w, uint32_t h) + { + m_width = w; + m_height = h; + } + + virtual void get_tile_size(uint32_t& w, uint32_t& h) const; + + virtual Error get_coded_image_colorspace(heif_colorspace* out_colorspace, heif_chroma* out_chroma) const; + + Error postprocess_coded_image_colorspace(heif_colorspace* inout_colorspace, heif_chroma* inout_chroma) const; + + virtual void process_before_write() { } + + // -- thumbnails + + void set_is_thumbnail() + { + m_is_thumbnail = true; + } + + void add_thumbnail(const std::shared_ptr& img) { m_thumbnails.push_back(img); } + + bool is_thumbnail() const { return m_is_thumbnail; } + + const std::vector>& get_thumbnails() const { return m_thumbnails; } + + + // --- alpha channel + + void set_is_alpha_channel() + { + m_is_alpha_channel = true; + } + + void set_alpha_channel(std::shared_ptr img) { m_alpha_channel = std::move(img); } + + bool is_alpha_channel() const { return m_is_alpha_channel; } + + const std::shared_ptr& get_alpha_channel() const { return m_alpha_channel; } + + void set_is_premultiplied_alpha(bool flag) { m_premultiplied_alpha = flag; } + + bool is_premultiplied_alpha() const { return m_premultiplied_alpha; } + + + // --- depth channel + + void set_is_depth_channel() + { + m_is_depth_channel = true; + } + + void set_depth_channel(std::shared_ptr img) { m_depth_channel = std::move(img); } + + bool is_depth_channel() const { return m_is_depth_channel; } + + const std::shared_ptr& get_depth_channel() const { return m_depth_channel; } + + + void set_depth_representation_info(struct heif_depth_representation_info& info) + { + m_has_depth_representation_info = true; + m_depth_representation_info = info; + } + + bool has_depth_representation_info() const + { + return m_has_depth_representation_info; + } + + const struct heif_depth_representation_info& get_depth_representation_info() const + { + return m_depth_representation_info; + } + + + // --- generic aux image + + void set_is_aux_image(const std::string& aux_type) + { + m_is_aux_image = true; + m_aux_image_type = aux_type; + } + + void add_aux_image(std::shared_ptr img) { m_aux_images.push_back(std::move(img)); } + + bool is_aux_image() const { return m_is_aux_image; } + + const std::string& get_aux_type() const { return m_aux_image_type; } + + std::vector> get_aux_images(int aux_image_filter = 0) const + { + if (aux_image_filter == 0) { + return m_aux_images; + } + else { + std::vector> auxImgs; + for (const auto& aux : m_aux_images) { + if ((aux_image_filter & LIBHEIF_AUX_IMAGE_FILTER_OMIT_ALPHA) && aux->is_alpha_channel()) { + continue; + } + + if ((aux_image_filter & LIBHEIF_AUX_IMAGE_FILTER_OMIT_DEPTH) && + aux->is_depth_channel()) { + continue; + } + + auxImgs.push_back(aux); + } + + return auxImgs; + } + } + + + // --- metadata + + void add_metadata(std::shared_ptr metadata) + { + m_metadata.push_back(std::move(metadata)); + } + + const std::vector>& get_metadata() const { return m_metadata; } + + + // --- miaf + + void mark_not_miaf_compatible() { m_miaf_compatible = false; } + + bool is_miaf_compatible() const { return m_miaf_compatible; } + + + // === decoding === + + virtual Error on_load_file() { return Error::Ok; } + + Error init_decoder_from_item(heif_item_id id); + + Result> decode_image(const struct heif_decoding_options& options, + bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0) const; + + virtual Result> decode_compressed_image(const struct heif_decoding_options& options, + bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0) const; + + virtual Result> get_compressed_image_data() const; + + Result>> get_properties() const; + + // === encoding === + + struct CodedImageData + { + std::vector> properties; + std::vector bitstream; + + // If 0, the encoded size is equal to the input size. + uint32_t encoded_image_width = 0; + uint32_t encoded_image_height = 0; + + void append(const uint8_t* data, size_t size); + + void append_with_4bytes_size(const uint8_t* data, size_t size); + }; + + Result encode_to_bitstream_and_boxes(const std::shared_ptr& image, + struct heif_encoder* encoder, + const struct heif_encoding_options& options, + enum heif_image_input_class input_class); + + Error encode_to_item(HeifContext* ctx, + const std::shared_ptr& image, + struct heif_encoder* encoder, + const struct heif_encoding_options& options, + enum heif_image_input_class input_class); + + const std::shared_ptr& get_color_profile_nclx() const { return m_color_profile_nclx; } + + const std::shared_ptr& get_color_profile_icc() const { return m_color_profile_icc; } + + void set_color_profile(const std::shared_ptr& profile) + { + auto icc = std::dynamic_pointer_cast(profile); + if (icc) { + m_color_profile_icc = std::move(icc); + } + + auto nclx = std::dynamic_pointer_cast(profile); + if (nclx) { + m_color_profile_nclx = std::move(nclx); + } + }; + + void set_intrinsic_matrix(const Box_cmin::RelativeIntrinsicMatrix& cmin) { + m_has_intrinsic_matrix = true; + m_intrinsic_matrix = cmin.to_absolute(get_ispe_width(), get_ispe_height()); + } + + bool has_intrinsic_matrix() const { return m_has_intrinsic_matrix; } + + Box_cmin::AbsoluteIntrinsicMatrix& get_intrinsic_matrix() { return m_intrinsic_matrix; } + + const Box_cmin::AbsoluteIntrinsicMatrix& get_intrinsic_matrix() const { return m_intrinsic_matrix; } + + + void set_extrinsic_matrix(const Box_cmex::ExtrinsicMatrix& cmex) { + m_has_extrinsic_matrix = true; + m_extrinsic_matrix = cmex; + } + + bool has_extrinsic_matrix() const { return m_has_extrinsic_matrix; } + + Box_cmex::ExtrinsicMatrix& get_extrinsic_matrix() { return m_extrinsic_matrix; } + + const Box_cmex::ExtrinsicMatrix& get_extrinsic_matrix() const { return m_extrinsic_matrix; } + + + void add_region_item_id(heif_item_id id) { m_region_item_ids.push_back(id); } + + const std::vector& get_region_item_ids() const { return m_region_item_ids; } + + + void add_decoding_warning(Error err) { m_decoding_warnings.emplace_back(std::move(err)); } + + const std::vector& get_decoding_warnings() const { return m_decoding_warnings; } + + virtual heif_image_tiling get_heif_image_tiling() const; + + Error process_image_transformations_on_tiling(heif_image_tiling&) const; + + Error transform_requested_tile_position_to_original_tile_position(uint32_t& tile_x, uint32_t& tile_y) const; + + virtual std::shared_ptr get_decoder() const { return nullptr; } + +private: + HeifContext* m_heif_context; + + heif_item_id m_id = 0; + uint32_t m_width = 0, m_height = 0; // after all transformations have been applied + bool m_is_primary = false; + + bool m_is_thumbnail = false; + + std::vector> m_thumbnails; + + bool m_is_alpha_channel = false; + bool m_premultiplied_alpha = false; + std::shared_ptr m_alpha_channel; + + bool m_is_depth_channel = false; + std::shared_ptr m_depth_channel; + + bool m_has_depth_representation_info = false; + struct heif_depth_representation_info m_depth_representation_info; + + bool m_is_aux_image = false; + std::string m_aux_image_type; + std::vector> m_aux_images; + + std::vector> m_metadata; + + std::shared_ptr m_color_profile_nclx; + std::shared_ptr m_color_profile_icc; + + bool m_miaf_compatible = true; + + std::vector m_region_item_ids; + + bool m_has_intrinsic_matrix = false; + Box_cmin::AbsoluteIntrinsicMatrix m_intrinsic_matrix{}; + + bool m_has_extrinsic_matrix = false; + Box_cmex::ExtrinsicMatrix m_extrinsic_matrix{}; + + std::vector m_decoding_warnings; + +protected: + Result> read_bitstream_configuration_data_override(heif_item_id itemId, heif_compression_format format) const; + + virtual Result encode(const std::shared_ptr& image, + struct heif_encoder* encoder, + const struct heif_encoding_options& options, + enum heif_image_input_class input_class) { return {}; } + + // --- encoding utility functions + + static void add_color_profile(const std::shared_ptr& image, + const struct heif_encoding_options& options, + enum heif_image_input_class input_class, + const heif_color_profile_nclx* target_heif_nclx, + ImageItem::CodedImageData& inout_codedImage); +}; + + +class ImageItem_Error : public ImageItem +{ +public: + // dummy ImageItem class that is a placeholder for unsupported item types + + ImageItem_Error(uint32_t item_type, heif_item_id id, Error err) + : ImageItem(nullptr, id), m_item_type(item_type), m_item_error(err) {} + + uint32_t get_infe_type() const override + { + return m_item_type; + } + + Error get_item_error() const override { return m_item_error; } + + [[nodiscard]] int get_luma_bits_per_pixel() const override { return -1; } + + [[nodiscard]] int get_chroma_bits_per_pixel() const override { return -1; } + +private: + uint32_t m_item_type; + Error m_item_error; +}; + +#endif //LIBHEIF_IMAGEITEM_H diff --git a/libheif/image-items/jpeg.cc b/libheif/image-items/jpeg.cc new file mode 100644 index 0000000000..ac22ba7472 --- /dev/null +++ b/libheif/image-items/jpeg.cc @@ -0,0 +1,138 @@ +/* + * HEIF JPEG codec. + * Copyright (c) 2023 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 "jpeg.h" +#include "codecs/jpeg_dec.h" +#include "codecs/jpeg_boxes.h" +#include "security_limits.h" +#include "pixelimage.h" +#include "api/libheif/api_structs.h" +#include + + +static uint8_t JPEG_SOS = 0xDA; + + +const heif_color_profile_nclx* ImageItem_JPEG::get_forced_output_nclx() const +{ + // JPEG always uses CCIR-601 + + static heif_color_profile_nclx target_heif_nclx; + target_heif_nclx.version = 1; + target_heif_nclx.matrix_coefficients = heif_matrix_coefficients_ITU_R_BT_601_6; + target_heif_nclx.color_primaries = heif_color_primaries_ITU_R_BT_601_6; + target_heif_nclx.transfer_characteristics = heif_transfer_characteristic_ITU_R_BT_601_6; + target_heif_nclx.full_range_flag = true; + + return &target_heif_nclx; +} + + +Result ImageItem_JPEG::encode(const std::shared_ptr& image, + struct heif_encoder* encoder, + const struct heif_encoding_options& options, + enum heif_image_input_class input_class) +{ + CodedImageData codedImage; + + + heif_image c_api_image; + c_api_image.image = image; + + struct heif_error err = encoder->plugin->encode_image(encoder->encoder, &c_api_image, input_class); + if (err.code) { + return Error(err.code, + err.subcode, + err.message); + } + + std::vector vec; + + for (;;) { + uint8_t* data; + int size; + + encoder->plugin->get_compressed_data(encoder->encoder, &data, &size, nullptr); + + if (data == nullptr) { + break; + } + + size_t oldsize = vec.size(); + vec.resize(oldsize + size); + memcpy(vec.data() + oldsize, data, size); + } + +#if 0 + // Optional: split the JPEG data into a jpgC box and the actual image data. + // Currently disabled because not supported yet in other decoders. + if (false) { + size_t pos = find_jpeg_marker_start(vec, JPEG_SOS); + if (pos > 0) { + std::vector jpgC_data(vec.begin(), vec.begin() + pos); + auto jpgC = std::make_shared(); + jpgC->set_data(jpgC_data); + + auto ipma_box = m_heif_file->get_ipma_box(); + int index = m_heif_file->get_ipco_box()->find_or_append_child_box(jpgC); + ipma_box->add_property_for_item_ID(image_id, Box_ipma::PropertyAssociation{true, uint16_t(index + 1)}); + + std::vector image_data(vec.begin() + pos, vec.end()); + vec = std::mo ve(image_data); + } + } +#endif + (void) JPEG_SOS; + + codedImage.bitstream = vec; + +#if 0 + // TODO: extract 'jpgC' header data +#endif + + return {codedImage}; +} + + +Result> ImageItem_JPEG::read_bitstream_configuration_data(heif_item_id itemId) const +{ + return m_decoder->read_bitstream_configuration_data(); +} + + +std::shared_ptr ImageItem_JPEG::get_decoder() const +{ + return m_decoder; +} + +Error ImageItem_JPEG::on_load_file() +{ + // Note: jpgC box is optional. NULL is a valid value. + auto jpgC_box = get_file()->get_property(get_id()); + + m_decoder = std::make_shared(jpgC_box); + + DataExtent extent; + extent.set_from_image_item(get_context()->get_heif_file(), get_id()); + + m_decoder->set_data_extent(extent); + + return Error::Ok; +} diff --git a/libheif/image-items/jpeg.h b/libheif/image-items/jpeg.h new file mode 100644 index 0000000000..ecc9bd7533 --- /dev/null +++ b/libheif/image-items/jpeg.h @@ -0,0 +1,63 @@ +/* + * HEIF JPEG codec. + * Copyright (c) 2023 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_JPEG_H +#define LIBHEIF_JPEG_H + +#include "box.h" +#include +#include +#include "image_item.h" +#include + + +class ImageItem_JPEG : public ImageItem +{ +public: + ImageItem_JPEG(HeifContext* ctx, heif_item_id id) : ImageItem(ctx, id) { } + + ImageItem_JPEG(HeifContext* ctx) : ImageItem(ctx) { } + + uint32_t get_infe_type() const override { return fourcc("jpeg"); } + + const heif_color_profile_nclx* get_forced_output_nclx() const override; + + heif_compression_format get_compression_format() const override { return heif_compression_JPEG; } + + + Error on_load_file() override; + +public: + + Result encode(const std::shared_ptr& image, + struct heif_encoder* encoder, + const struct heif_encoding_options& options, + enum heif_image_input_class input_class) override; + +protected: + std::shared_ptr get_decoder() const override; + + Result> read_bitstream_configuration_data(heif_item_id itemId) const override; + +private: + std::shared_ptr m_decoder; +}; + +#endif // LIBHEIF_JPEG_H diff --git a/libheif/image-items/jpeg2000.cc b/libheif/image-items/jpeg2000.cc new file mode 100644 index 0000000000..984e5aa097 --- /dev/null +++ b/libheif/image-items/jpeg2000.cc @@ -0,0 +1,112 @@ +/* + * HEIF JPEG 2000 codec. + * Copyright (c) 2023 Brad Hards + * + * 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 "jpeg2000.h" +#include "libheif/api_structs.h" +#include "codecs/jpeg2000_dec.h" +#include "codecs/jpeg2000_boxes.h" +#include +#include +#include + + + +Result ImageItem_JPEG2000::encode(const std::shared_ptr& image, + struct heif_encoder* encoder, + const struct heif_encoding_options& options, + enum heif_image_input_class input_class) +{ + CodedImageData codedImageData; + + heif_image c_api_image; + c_api_image.image = image; + + encoder->plugin->encode_image(encoder->encoder, &c_api_image, input_class); + + // get compressed data + for (;;) { + uint8_t* data; + int size; + + encoder->plugin->get_compressed_data(encoder->encoder, &data, &size, nullptr); + + if (data == nullptr) { + break; + } + + codedImageData.append(data, size); + } + + // add 'j2kH' property + auto j2kH = std::make_shared(); + + // add 'cdef' to 'j2kH' + auto cdef = std::make_shared(); + cdef->set_channels(image->get_colorspace()); + j2kH->append_child_box(cdef); + + codedImageData.properties.push_back(j2kH); + + return codedImageData; +} + + +Result> ImageItem_JPEG2000::read_bitstream_configuration_data(heif_item_id itemId) const +{ + // --- get codec configuration + + std::shared_ptr j2kH_box = get_file()->get_property(itemId); + if (!j2kH_box) + { + // TODO - Correctly Find the j2kH box + // return Error(heif_error_Invalid_input, + // heif_suberror_Unspecified); + } + // else if (!j2kH_box->get_headers(data)) { + // return Error(heif_error_Invalid_input, + // heif_suberror_No_item_data); + // } + + return std::vector{}; +} + +std::shared_ptr ImageItem_JPEG2000::get_decoder() const +{ + return m_decoder; +} + +Error ImageItem_JPEG2000::on_load_file() +{ + auto j2kH = get_file()->get_property(get_id()); + if (!j2kH) { + return Error{heif_error_Invalid_input, + heif_suberror_Unspecified, + "No j2kH box found."}; + } + + m_decoder = std::make_shared(j2kH); + + DataExtent extent; + extent.set_from_image_item(get_context()->get_heif_file(), get_id()); + + m_decoder->set_data_extent(extent); + + return Error::Ok; +} diff --git a/libheif/image-items/jpeg2000.h b/libheif/image-items/jpeg2000.h new file mode 100644 index 0000000000..be6471dfc1 --- /dev/null +++ b/libheif/image-items/jpeg2000.h @@ -0,0 +1,61 @@ +/* + * HEIF JPEG 2000 codec. + * Copyright (c) 2023 Brad Hards + * + * 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_JPEG2000_H +#define LIBHEIF_JPEG2000_H + +#include "box.h" +#include "file.h" +#include "context.h" +#include +#include +#include +#include + + +class ImageItem_JPEG2000 : public ImageItem +{ +public: + ImageItem_JPEG2000(HeifContext* ctx, heif_item_id id) : ImageItem(ctx, id) {} + + ImageItem_JPEG2000(HeifContext* ctx) : ImageItem(ctx) {} + + uint32_t get_infe_type() const override { return fourcc("j2k1"); } + + heif_compression_format get_compression_format() const override { return heif_compression_JPEG2000; } + + Result encode(const std::shared_ptr& image, + struct heif_encoder* encoder, + const struct heif_encoding_options& options, + enum heif_image_input_class input_class) override; + +protected: + Result> read_bitstream_configuration_data(heif_item_id itemId) const override; + + std::shared_ptr get_decoder() const override; + +public: + Error on_load_file() override; + +private: + std::shared_ptr m_decoder; +}; + +#endif // LIBHEIF_JPEG2000_H diff --git a/libheif/codecs/mask_image.cc b/libheif/image-items/mask_image.cc similarity index 57% rename from libheif/codecs/mask_image.cc rename to libheif/image-items/mask_image.cc index fc977818b4..98ddb06501 100644 --- a/libheif/codecs/mask_image.cc +++ b/libheif/image-items/mask_image.cc @@ -20,7 +20,6 @@ * along with libheif. If not, see . */ - #include #include #include @@ -30,8 +29,11 @@ #include "libheif/heif.h" #include "logging.h" #include "mask_image.h" +#include "image_item.h" +#include "security_limits.h" + -Error Box_mskC::parse(BitstreamRange& range) +Error Box_mskC::parse(BitstreamRange& range, const heif_security_limits* limits) { parse_full_box_header(range); m_bits_per_pixel = range.read8(); @@ -55,46 +57,28 @@ Error Box_mskC::write(StreamWriter& writer) const } -Error MaskImageCodec::decode_mask_image(const std::shared_ptr& heif_file, +Error MaskImageCodec::decode_mask_image(const HeifContext* context, heif_item_id ID, std::shared_ptr& img, - uint32_t maximum_image_width_limit, - uint32_t maximum_image_height_limit, const std::vector& data) { - std::vector> item_properties; - Error error = heif_file->get_properties(ID, item_properties); - if (error) { - return error; - } - std::shared_ptr mskC; + std::shared_ptr ispe = context->get_heif_file()->get_property(ID); + std::shared_ptr mskC = context->get_heif_file()->get_property(ID); + uint32_t width = 0; uint32_t height = 0; - bool found_ispe = false; - for (const auto& prop : item_properties) { - auto ispe = std::dynamic_pointer_cast(prop); - if (ispe) { - width = ispe->get_width(); - height = ispe->get_height(); - - if (width >= maximum_image_width_limit || height >= maximum_image_height_limit) { - std::stringstream sstr; - sstr << "Image size " << width << "x" << height << " exceeds the maximum image size " - << maximum_image_width_limit << "x" << maximum_image_height_limit << "\n"; - - return Error(heif_error_Memory_allocation_error, - heif_suberror_Security_limit_exceeded, - sstr.str()); - } - found_ispe = true; - } - auto maybe_mskC = std::dynamic_pointer_cast(prop); - if (maybe_mskC) { - mskC = maybe_mskC; + if (ispe) { + width = ispe->get_width(); + height = ispe->get_height(); + + Error error = check_for_valid_image_size(context->get_security_limits(), width, height); + if (error) { + return error; } } - if (!found_ispe || !mskC) { + + if (!ispe || !mskC) { return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, "Missing required box for mask codec"); @@ -116,7 +100,7 @@ Error MaskImageCodec::decode_mask_image(const std::shared_ptr& h img = std::make_shared(); img->create(width, height, heif_colorspace_monochrome, heif_chroma_monochrome); img->add_plane(heif_channel_Y, width, height, mskC->get_bits_per_pixel()); - int stride; + uint32_t stride; uint8_t* dst = img->get_plane(heif_channel_Y, &stride); if (((uint32_t)stride) == width) { memcpy(dst, data.data(), data.size()); @@ -131,53 +115,88 @@ Error MaskImageCodec::decode_mask_image(const std::shared_ptr& h return Error::Ok; } -Error MaskImageCodec::encode_mask_image(const std::shared_ptr& heif_file, - const std::shared_ptr& src_image, - void* encoder_struct, - const struct heif_encoding_options& options, - std::shared_ptr& out_image) + +Result> ImageItem_mask::decode_compressed_image(const struct heif_decoding_options& options, + bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0) const { - if (src_image->get_colorspace() != heif_colorspace_monochrome) + std::shared_ptr img; + + std::vector data; + + // image data, usually from 'mdat' + + Error error = get_file()->append_data_from_iloc(get_id(), data); + if (error) { + return error; + } + + Error err = MaskImageCodec::decode_mask_image(get_context(), + get_id(), + img, + data); + if (err) { + return err; + } + else { + return img; + } +} + + +Result ImageItem_mask::encode(const std::shared_ptr& image, + struct heif_encoder* encoder, + const struct heif_encoding_options& options, + enum heif_image_input_class input_class) +{ + CodedImageData codedImageData; + + if (image->get_colorspace() != heif_colorspace_monochrome) { return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, "Unsupported colourspace for mask region"); } - if (src_image->get_bits_per_pixel(heif_channel_Y) != 8) + + if (image->get_bits_per_pixel(heif_channel_Y) != 8) { return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, "Unsupported bit depth for mask region"); } + // TODO: we could add an option to lossless-compress this data std::vector data; - int src_stride; - uint8_t* src_data = src_image->get_plane(heif_channel_Y, &src_stride); + uint32_t src_stride; + uint8_t* src_data = image->get_plane(heif_channel_Y, &src_stride); - int w = src_image->get_width(); - int h = src_image->get_height(); + uint32_t w = image->get_width(); + uint32_t h = image->get_height(); data.resize(w * h); - if (w == src_stride) { - memcpy(data.data(), src_data, w * h); + if (w == (uint32_t)src_stride) { + codedImageData.append(src_data, w*h); } else { - for (int y = 0; y < h; y++) { - memcpy(data.data() + y * w, src_data + y * src_stride, w); + for (uint32_t y = 0; y < h; y++) { + codedImageData.append(src_data + y * src_stride, w); } } - heif_file->append_iloc_data(out_image->get_id(), data, 0); - std::shared_ptr mskC = std::make_shared(); - mskC->set_bits_per_pixel(src_image->get_bits_per_pixel(heif_channel_Y)); - heif_file->add_property(out_image->get_id(), mskC, true); + mskC->set_bits_per_pixel(image->get_bits_per_pixel(heif_channel_Y)); + codedImageData.properties.push_back(mskC); - // We need to ensure ispe is essential for the mask case - std::shared_ptr ispe = std::make_shared(); - ispe->set_size(src_image->get_width(), src_image->get_height()); - heif_file->add_property(out_image->get_id(), ispe, true); + return codedImageData; +} - return Error::Ok; + +int ImageItem_mask::get_luma_bits_per_pixel() const +{ + auto mskC = get_file()->get_property(get_id()); + if (!mskC) { + return -1; + } + + return mskC->get_bits_per_pixel(); } diff --git a/libheif/codecs/mask_image.h b/libheif/image-items/mask_image.h similarity index 58% rename from libheif/codecs/mask_image.h rename to libheif/image-items/mask_image.h index a05842128d..c61befdb2c 100644 --- a/libheif/codecs/mask_image.h +++ b/libheif/image-items/mask_image.h @@ -50,6 +50,8 @@ class Box_mskC : public FullBox set_short_type(fourcc("mskC")); } + bool is_essential() const override { return true; } + std::string dump(Indent&) const override; Error write(StreamWriter& writer) const override; @@ -61,7 +63,7 @@ class Box_mskC : public FullBox { m_bits_per_pixel = bits_per_pixel; } protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits* limits) override; private: uint8_t m_bits_per_pixel; @@ -70,18 +72,40 @@ class Box_mskC : public FullBox class MaskImageCodec { public: - static Error decode_mask_image(const std::shared_ptr& heif_file, + static Error decode_mask_image(const HeifContext* context, heif_item_id ID, std::shared_ptr& img, - uint32_t maximum_image_width_limit, - uint32_t maximum_image_height_limit, const std::vector& data); - static Error encode_mask_image(const std::shared_ptr& heif_file, - const std::shared_ptr& src_image, - void* encoder_struct, - const struct heif_encoding_options& options, - std::shared_ptr& out_image); }; -#endif //LIBHEIF_MASK_IMAGE_H + +class ImageItem_mask : public ImageItem +{ +public: + ImageItem_mask(HeifContext* ctx, heif_item_id id) : ImageItem(ctx, id) {} + + ImageItem_mask(HeifContext* ctx) : ImageItem(ctx) {} + + uint32_t get_infe_type() const override { return fourcc("mski"); } + + const heif_color_profile_nclx* get_forced_output_nclx() const override { return nullptr; } + + heif_compression_format get_compression_format() const override { return heif_compression_mask; } + + bool is_ispe_essential() const override { return true; } + + int get_luma_bits_per_pixel() const override; + + int get_chroma_bits_per_pixel() const override { return 0; } + + Result> decode_compressed_image(const struct heif_decoding_options& options, + bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0) const override; + + Result encode(const std::shared_ptr& image, + struct heif_encoder* encoder, + const struct heif_encoding_options& options, + enum heif_image_input_class input_class) override; +}; + +#endif //LIBHEIF_MASK_IMAGE_H diff --git a/libheif/image-items/overlay.cc b/libheif/image-items/overlay.cc new file mode 100644 index 0000000000..df91f83e81 --- /dev/null +++ b/libheif/image-items/overlay.cc @@ -0,0 +1,439 @@ +/* + * HEIF codec. + * Copyright (c) 2024 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 "overlay.h" +#include "context.h" +#include "file.h" +#include "color-conversion/colorconversion.h" +#include "security_limits.h" + + +template +void writevec(uint8_t* data, size_t& idx, I value, int len) +{ + for (int i = 0; i < len; i++) { + data[idx + i] = static_cast((value >> (len - 1 - i) * 8) & 0xFF); + } + + idx += len; +} + + +static int32_t readvec_signed(const std::vector& data, int& ptr, int len) +{ + const uint32_t high_bit = 0x80 << ((len - 1) * 8); + + uint32_t val = 0; + while (len--) { + val <<= 8; + val |= data[ptr++]; + } + + bool negative = (val & high_bit) != 0; + val &= ~high_bit; + + if (negative) { + return -(high_bit - val); + } + else { + return val; + } + + return val; +} + + +static uint32_t readvec(const std::vector& data, int& ptr, int len) +{ + uint32_t val = 0; + while (len--) { + val <<= 8; + val |= data[ptr++]; + } + + return val; +} + + +Error ImageOverlay::parse(size_t num_images, const std::vector& data) +{ + Error eofError(heif_error_Invalid_input, + heif_suberror_Invalid_overlay_data, + "Overlay image data incomplete"); + + if (data.size() < 2 + 4 * 2) { + return eofError; + } + + m_version = data[0]; + if (m_version != 0) { + std::stringstream sstr; + sstr << "Overlay image data version " << ((int) m_version) << " is not implemented yet"; + + return {heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + sstr.str()}; + } + + m_flags = data[1]; + + int field_len = ((m_flags & 1) ? 4 : 2); + int ptr = 2; + + if (ptr + 4 * 2 + 2 * field_len + num_images * 2 * field_len > data.size()) { + return eofError; + } + + for (int i = 0; i < 4; i++) { + uint16_t color = static_cast(readvec(data, ptr, 2)); + m_background_color[i] = color; + } + + m_width = readvec(data, ptr, field_len); + m_height = readvec(data, ptr, field_len); + + if (m_width == 0 || m_height == 0) { + return {heif_error_Invalid_input, + heif_suberror_Invalid_overlay_data, + "Overlay image with zero width or height."}; + } + + m_offsets.resize(num_images); + + for (size_t i = 0; i < num_images; i++) { + m_offsets[i].x = readvec_signed(data, ptr, field_len); + m_offsets[i].y = readvec_signed(data, ptr, field_len); + } + + return Error::Ok; +} + + +std::vector ImageOverlay::write() const +{ + assert(m_version == 0); + + bool longFields = (m_width > 0xFFFF) || (m_height > 0xFFFF); + for (const auto& img : m_offsets) { + if (img.x > 0x7FFF || img.y > 0x7FFF || img.x < -32768 || img.y < -32768) { + longFields = true; + break; + } + } + + std::vector data; + + data.resize(2 + 4 * 2 + (longFields ? 4 : 2) * (2 + m_offsets.size() * 2)); + + size_t idx = 0; + data[idx++] = m_version; + data[idx++] = (longFields ? 1 : 0); // flags + + for (uint16_t color : m_background_color) { + writevec(data.data(), idx, color, 2); + } + + writevec(data.data(), idx, m_width, longFields ? 4 : 2); + writevec(data.data(), idx, m_height, longFields ? 4 : 2); + + for (const auto& img : m_offsets) { + writevec(data.data(), idx, img.x, longFields ? 4 : 2); + writevec(data.data(), idx, img.y, longFields ? 4 : 2); + } + + assert(idx == data.size()); + + return data; +} + + +std::string ImageOverlay::dump() const +{ + std::stringstream sstr; + + sstr << "version: " << ((int) m_version) << "\n" + << "flags: " << ((int) m_flags) << "\n" + << "background color: " << m_background_color[0] + << ";" << m_background_color[1] + << ";" << m_background_color[2] + << ";" << m_background_color[3] << "\n" + << "canvas size: " << m_width << "x" << m_height << "\n" + << "offsets: "; + + for (const ImageWithOffset& offset : m_offsets) { + sstr << offset.x << ";" << offset.y << " "; + } + sstr << "\n"; + + return sstr.str(); +} + + +void ImageOverlay::get_background_color(uint16_t col[4]) const +{ + for (int i = 0; i < 4; i++) { + col[i] = m_background_color[i]; + } +} + + +void ImageOverlay::get_offset(size_t image_index, int32_t* x, int32_t* y) const +{ + assert(image_index < m_offsets.size()); + assert(x && y); + + *x = m_offsets[image_index].x; + *y = m_offsets[image_index].y; +} + + + +ImageItem_Overlay::ImageItem_Overlay(HeifContext* ctx) + : ImageItem(ctx) +{ +} + + +ImageItem_Overlay::ImageItem_Overlay(HeifContext* ctx, heif_item_id id) + : ImageItem(ctx, id) +{ +} + + +Error ImageItem_Overlay::on_load_file() +{ + Error err = read_overlay_spec(); + if (err) { + return err; + } + + return Error::Ok; +} + + +Error ImageItem_Overlay::read_overlay_spec() +{ + auto heif_file = get_context()->get_heif_file(); + + auto iref_box = heif_file->get_iref_box(); + + if (!iref_box) { + return {heif_error_Invalid_input, + heif_suberror_No_iref_box, + "No iref box available, but needed for iovl image"}; + } + + + m_overlay_image_ids = iref_box->get_references(get_id(), fourcc("dimg")); + + /* TODO: probably, it is valid that an iovl image has no references ? + + if (image_references.empty()) { + return Error(heif_error_Invalid_input, + heif_suberror_Missing_grid_images, + "'iovl' image with more than one reference image"); + } + */ + + + std::vector overlay_data; + Error err = heif_file->get_uncompressed_item_data(get_id(), &overlay_data); + if (err) { + return err; + } + + err = m_overlay_spec.parse(m_overlay_image_ids.size(), overlay_data); + if (err) { + return err; + } + + if (m_overlay_image_ids.size() != m_overlay_spec.get_num_offsets()) { + return Error(heif_error_Invalid_input, + heif_suberror_Invalid_overlay_data, + "Number of image offsets does not match the number of image references"); + } + + return Error::Ok; +} + + +Result> ImageItem_Overlay::decode_compressed_image(const struct heif_decoding_options& options, + bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0) const +{ + return decode_overlay_image(options); +} + + +Result> ImageItem_Overlay::decode_overlay_image(const heif_decoding_options& options) const +{ + std::shared_ptr img; + + uint32_t w = m_overlay_spec.get_canvas_width(); + uint32_t h = m_overlay_spec.get_canvas_height(); + + Error err = check_for_valid_image_size(get_context()->get_security_limits(), w, h); + if (err) { + return err; + } + + // TODO: seems we always have to compose this in RGB since the background color is an RGB value + img = std::make_shared(); + img->create(w, h, + heif_colorspace_RGB, + heif_chroma_444); + img->add_plane(heif_channel_R, w, h, 8); // TODO: other bit depths + img->add_plane(heif_channel_G, w, h, 8); // TODO: other bit depths + img->add_plane(heif_channel_B, w, h, 8); // TODO: other bit depths + + uint16_t bkg_color[4]; + m_overlay_spec.get_background_color(bkg_color); + + err = img->fill_RGB_16bit(bkg_color[0], bkg_color[1], bkg_color[2], bkg_color[3]); + if (err) { + return err; + } + + for (size_t i = 0; i < m_overlay_image_ids.size(); i++) { + + // detect if 'iovl' is referencing itself + + if (m_overlay_image_ids[i] == get_id()) { + return Error{heif_error_Invalid_input, + heif_suberror_Unspecified, + "Self-reference in 'iovl' image item."}; + } + + auto imgItem = get_context()->get_image(m_overlay_image_ids[i], true); + if (!imgItem) { + return Error(heif_error_Invalid_input, heif_suberror_Nonexisting_item_referenced, "'iovl' image references a non-existing item."); + } + if (auto error = imgItem->get_item_error()) { + return error; + } + + auto decodeResult = imgItem->decode_image(options, false, 0,0); + if (decodeResult.error) { + return decodeResult.error; + } + + std::shared_ptr overlay_img = decodeResult.value; + + + // process overlay in RGB space + + if (overlay_img->get_colorspace() != heif_colorspace_RGB || + overlay_img->get_chroma_format() != heif_chroma_444) { + overlay_img = convert_colorspace(overlay_img, heif_colorspace_RGB, heif_chroma_444, nullptr, 0, options.color_conversion_options); + if (!overlay_img) { + return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_color_conversion); + } + } + + int32_t dx, dy; + m_overlay_spec.get_offset(i, &dx, &dy); + + err = img->overlay(overlay_img, dx, dy); + if (err) { + if (err.error_code == heif_error_Invalid_input && + err.sub_error_code == heif_suberror_Overlay_image_outside_of_canvas) { + // NOP, ignore this error + } + else { + return err; + } + } + } + + return img; +} + + +int ImageItem_Overlay::get_luma_bits_per_pixel() const +{ + heif_item_id child; + Error err = get_context()->get_id_of_non_virtual_child_image(get_id(), child); + if (err) { + return -1; + } + + auto image = get_context()->get_image(child, true); + return image->get_luma_bits_per_pixel(); +} + + +int ImageItem_Overlay::get_chroma_bits_per_pixel() const +{ + heif_item_id child; + Error err = get_context()->get_id_of_non_virtual_child_image(get_id(), child); + if (err) { + return -1; + } + + auto image = get_context()->get_image(child, true); + return image->get_chroma_bits_per_pixel(); +} + + +Result> ImageItem_Overlay::add_new_overlay_item(HeifContext* ctx, const ImageOverlay& overlayspec) +{ + if (overlayspec.get_num_offsets() > 0xFFFF) { + return Error{heif_error_Usage_error, + heif_suberror_Unspecified, + "Too many overlay images (maximum: 65535)"}; + } + + std::vector ref_ids; + + auto file = ctx->get_heif_file(); + + for (const auto& overlay : overlayspec.get_overlay_stack()) { + file->get_infe_box(overlay.image_id)->set_hidden_item(true); // only show the full overlay + ref_ids.push_back(overlay.image_id); + } + + + // Create ImageOverlay + + std::vector iovl_data = overlayspec.write(); + + // Create IOVL Item + + heif_item_id iovl_id = file->add_new_image(fourcc("iovl")); + std::shared_ptr iovl_image = std::make_shared(ctx, iovl_id); + ctx->insert_image_item(iovl_id, iovl_image); + const int construction_method = 1; // 0=mdat 1=idat + file->append_iloc_data(iovl_id, iovl_data, construction_method); + + // Connect images to overlay + file->add_iref_reference(iovl_id, fourcc("dimg"), ref_ids); + + // Add ISPE property + file->add_ispe_property(iovl_id, overlayspec.get_canvas_width(), overlayspec.get_canvas_height(), false); + + // Add PIXI property (copy from first image) - According to MIAF, all images shall have the same color information. + auto pixi = file->get_property(ref_ids[0]); + file->add_property(iovl_id, pixi, true); + + // Set Brands + //m_heif_file->set_brand(encoder->plugin->compression_format, + // out_grid_image->is_miaf_compatible()); + + return iovl_image; +} diff --git a/libheif/image-items/overlay.h b/libheif/image-items/overlay.h new file mode 100644 index 0000000000..e3d42c2817 --- /dev/null +++ b/libheif/image-items/overlay.h @@ -0,0 +1,133 @@ +/* + * HEIF codec. + * Copyright (c) 2024 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_OVERLAY_H +#define LIBHEIF_OVERLAY_H + +#include "image_item.h" +#include +#include +#include + + +class ImageOverlay +{ +public: + Error parse(size_t num_images, const std::vector& data); + + std::vector write() const; + + std::string dump() const; + + void get_background_color(uint16_t col[4]) const; + + uint32_t get_canvas_width() const { return m_width; } + + uint32_t get_canvas_height() const { return m_height; } + + size_t get_num_offsets() const { return m_offsets.size(); } + + void get_offset(size_t image_index, int32_t* x, int32_t* y) const; + + void set_background_color(const uint16_t rgba_color[4]) + { + for (int i = 0; i < 4; i++) { + m_background_color[i] = rgba_color[i]; + } + } + + void set_canvas_size(uint32_t width, uint32_t height) + { + m_width = width; + m_height = height; + } + + void add_image_on_top(heif_item_id image_id, int32_t offset_x, int32_t offset_y) + { + m_offsets.emplace_back(ImageWithOffset{image_id, offset_x, offset_y}); + } + + struct ImageWithOffset + { + heif_item_id image_id; + int32_t x, y; + }; + + const std::vector& get_overlay_stack() const { return m_offsets; } + +private: + uint8_t m_version = 0; + uint8_t m_flags = 0; + uint16_t m_background_color[4]{0, 0, 0, 0}; + uint32_t m_width = 0; + uint32_t m_height = 0; + + std::vector m_offsets; +}; + + +class ImageItem_Overlay : public ImageItem +{ +public: + ImageItem_Overlay(HeifContext* ctx, heif_item_id id); + + ImageItem_Overlay(HeifContext* ctx); + + uint32_t get_infe_type() const override { return fourcc("iovl"); } + + // TODO: nclx depends on contained format + // const heif_color_profile_nclx* get_forced_output_nclx() const override { return nullptr; } + + // heif_compression_format get_compression_format() const override { return heif_compression_HEVC; } + + Error on_load_file() override; + + int get_luma_bits_per_pixel() const override; + + int get_chroma_bits_per_pixel() const override; + + Result encode(const std::shared_ptr& image, + struct heif_encoder* encoder, + const struct heif_encoding_options& options, + enum heif_image_input_class input_class) override + { + return Error{heif_error_Unsupported_feature, + heif_suberror_Unspecified, "Cannot encode image to 'iovl'"}; + } + + Result> decode_compressed_image(const struct heif_decoding_options& options, + bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0) const override; + + + // --- iovl specific + + static Result> add_new_overlay_item(HeifContext* ctx, const ImageOverlay& overlayspec); + +private: + ImageOverlay m_overlay_spec; + std::vector m_overlay_image_ids; + + Error read_overlay_spec(); + + Result> decode_overlay_image(const heif_decoding_options& options) const; +}; + + +#endif //LIBHEIF_OVERLAY_H diff --git a/libheif/image-items/tiled.cc b/libheif/image-items/tiled.cc new file mode 100644 index 0000000000..7bf2b93d72 --- /dev/null +++ b/libheif/image-items/tiled.cc @@ -0,0 +1,793 @@ +/* + * HEIF codec. + * Copyright (c) 2024 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 "tiled.h" +#include "context.h" +#include "file.h" +#include +#include "security_limits.h" +#include "codecs/hevc_dec.h" +#include "libheif/api_structs.h" + + +static uint64_t readvec(const std::vector& data, size_t& ptr, int len) +{ + uint64_t val = 0; + while (len--) { + val <<= 8; + val |= data[ptr++]; + } + + return val; +} + + +uint64_t number_of_tiles(const heif_tiled_image_parameters& params) +{ + uint64_t nTiles = nTiles_h(params) * static_cast(nTiles_v(params)); + + for (int i = 0; i < params.number_of_extra_dimensions; i++) { + // We only support up to 8 extra dimensions + if (i == 8) { + break; + } + + nTiles *= params.extra_dimensions[i]; + } + + return nTiles; +} + + +uint32_t nTiles_h(const heif_tiled_image_parameters& params) +{ + return (params.image_width + params.tile_width - 1) / params.tile_width; +} + + +uint32_t nTiles_v(const heif_tiled_image_parameters& params) +{ + return (params.image_height + params.tile_height - 1) / params.tile_height; +} + + +void Box_tilC::derive_box_version() +{ + set_version(1); + + uint8_t flags = 0; + + switch (m_parameters.offset_field_length) { + case 32: + flags |= 0; + break; + case 40: + flags |= 0x01; + break; + case 48: + flags |= 0x02; + break; + case 64: + flags |= 0x03; + break; + default: + assert(false); // TODO: return error + } + + switch (m_parameters.size_field_length) { + case 0: + flags |= 0; + break; + case 24: + flags |= 0x04; + break; + case 32: + flags |= 0x08; + break; + case 64: + flags |= 0x0c; + break; + default: + assert(false); // TODO: return error + } + + if (m_parameters.tiles_are_sequential) { + flags |= 0x10; + } + + set_flags(flags); +} + + +Error Box_tilC::write(StreamWriter& writer) const +{ + assert(m_parameters.version == 1); + + size_t box_start = reserve_box_header_space(writer); + + if (m_parameters.number_of_extra_dimensions > 8) { + assert(false); // currently not supported + } + + writer.write32(m_parameters.tile_width); + writer.write32(m_parameters.tile_height); + writer.write32(m_parameters.compression_format_fourcc); + + writer.write8(m_parameters.number_of_extra_dimensions); + + for (int i = 0; i < m_parameters.number_of_extra_dimensions; i++) { + writer.write32(m_parameters.extra_dimensions[i]); + } + + prepend_header(writer, box_start); + + return Error::Ok; +} + + +std::string Box_tilC::dump(Indent& indent) const +{ + std::ostringstream sstr; + + sstr << BoxHeader::dump(indent); + + sstr << indent << "version: " << ((int) get_version()) << "\n" + //<< indent << "image size: " << m_parameters.image_width << "x" << m_parameters.image_height << "\n" + << indent << "tile size: " << m_parameters.tile_width << "x" << m_parameters.tile_height << "\n" + << indent << "compression: " << fourcc_to_string(m_parameters.compression_format_fourcc) << "\n" + << indent << "tiles are sequential: " << (m_parameters.tiles_are_sequential ? "yes" : "no") << "\n" + << indent << "offset field length: " << ((int) m_parameters.offset_field_length) << " bits\n" + << indent << "size field length: " << ((int) m_parameters.size_field_length) << " bits\n" + << indent << "number of extra dimensions: " << ((int) m_parameters.number_of_extra_dimensions) << "\n"; + + return sstr.str(); + +} + + +Error Box_tilC::parse(BitstreamRange& range, const heif_security_limits* limits) +{ + parse_full_box_header(range); + + if (get_version() != 1) { + std::stringstream sstr; + sstr << "'tild' image version " << ((int) get_version()) << " is not implemented yet"; + + return {heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + sstr.str()}; + } + + m_parameters.version = get_version(); + + uint32_t flags = get_flags(); + + switch (flags & 0x03) { + case 0: + m_parameters.offset_field_length = 32; + break; + case 1: + m_parameters.offset_field_length = 40; + break; + case 2: + m_parameters.offset_field_length = 48; + break; + case 3: + m_parameters.offset_field_length = 64; + break; + } + + switch (flags & 0x0c) { + case 0x00: + m_parameters.size_field_length = 0; + break; + case 0x04: + m_parameters.size_field_length = 24; + break; + case 0x08: + m_parameters.size_field_length = 32; + break; + case 0xc0: + m_parameters.size_field_length = 64; + break; + } + + m_parameters.tiles_are_sequential = !!(flags & 0x10); + + + m_parameters.tile_width = range.read32(); + m_parameters.tile_height = range.read32(); + m_parameters.compression_format_fourcc = range.read32(); + + if (m_parameters.tile_width == 0 || m_parameters.tile_height == 0) { + return {heif_error_Invalid_input, + heif_suberror_Unspecified, + "Tile with zero width or height."}; + } + + + // --- extra dimensions + + m_parameters.number_of_extra_dimensions = range.read8(); + + for (int i = 0; i < m_parameters.number_of_extra_dimensions; i++) { + uint32_t size = range.read32(); + + if (size == 0) { + return {heif_error_Invalid_input, + heif_suberror_Unspecified, + "'tild' extra dimension may not be zero."}; + } + + if (i < 8) { + m_parameters.extra_dimensions[i] = size; + } + else { + // TODO: error: too many dimensions (not supported) + } + } + + + return range.get_error(); +} + + +Error TildHeader::set_parameters(const heif_tiled_image_parameters& params) +{ + m_parameters = params; + + auto max_tiles = heif_get_global_security_limits()->max_number_of_tiles; + + if (max_tiles && number_of_tiles(params) > max_tiles) { + return {heif_error_Unsupported_filetype, + heif_suberror_Security_limit_exceeded, + "Number of tiles exceeds security limit"}; + } + + m_offsets.resize(number_of_tiles(params)); + + for (auto& tile: m_offsets) { + tile.offset = TILD_OFFSET_NOT_LOADED; + } + + return Error::Ok; +} + + +Error TildHeader::read_full_offset_table(const std::shared_ptr& file, heif_item_id tild_id, const heif_security_limits* limits) +{ + auto max_tiles = heif_get_global_security_limits()->max_number_of_tiles; + + uint64_t nTiles = number_of_tiles(m_parameters); + if (max_tiles && nTiles > max_tiles) { + return {heif_error_Invalid_input, + heif_suberror_Security_limit_exceeded, + "Number of tiles exceeds security limit."}; + } + + return read_offset_table_range(file, tild_id, 0, nTiles); +} + + +Error TildHeader::read_offset_table_range(const std::shared_ptr& file, heif_item_id tild_id, + uint64_t start, uint64_t end) +{ + const Error eofError(heif_error_Invalid_input, + heif_suberror_Unspecified, + "Tild header data incomplete"); + + std::vector data; + + + + // --- load offsets + + size_t size_to_read = (end - start) * (m_parameters.offset_field_length + m_parameters.size_field_length) / 8; + size_t start_offset = start * (m_parameters.offset_field_length + m_parameters.size_field_length) / 8; + + // TODO: when we request a file range from the stream reader, it may return a larger range. + // We should then also use this larger range to read more table entries. + // But this is not easy since our data may span several iloc extents and we have to map this back to item addresses. + // Maybe it is easier to just ignore the extra data and rely on the stream read to cache this data. + + Error err = file->append_data_from_iloc(tild_id, data, start_offset, size_to_read); + if (err) { + return err; + } + + size_t idx = 0; + for (uint64_t i = start; i < end; i++) { + m_offsets[i].offset = readvec(data, idx, m_parameters.offset_field_length / 8); + + if (m_parameters.size_field_length) { + assert(m_parameters.size_field_length <= 32); + m_offsets[i].size = static_cast(readvec(data, idx, m_parameters.size_field_length / 8)); + } + + // printf("[%zu] : offset/size: %zu %d\n", i, m_offsets[i].offset, m_offsets[i].size); + } + + return Error::Ok; +} + + +size_t TildHeader::get_header_size() const +{ + assert(m_header_size); + return m_header_size; +} + + +uint32_t TildHeader::get_offset_table_entry_size() const +{ + return (m_parameters.offset_field_length + m_parameters.size_field_length) / 8; +} + + +std::pair TildHeader::get_tile_offset_table_range_to_read(uint32_t idx, uint32_t nEntries) const +{ + uint32_t start = idx; + uint32_t end = idx+1; + + while (end < m_offsets.size() && end - idx < nEntries && m_offsets[end].offset == TILD_OFFSET_NOT_LOADED) { + end++; + } + + while (start > 0 && idx - start < nEntries && m_offsets[start-1].offset == TILD_OFFSET_NOT_LOADED) { + start--; + } + + // try to fill the smaller hole + + if (end - start > nEntries) { + if (idx - start < end - idx) { + end = start + nEntries; + } + else { + start = end - nEntries; + } + } + + return {start, end}; +} + + +void TildHeader::set_tild_tile_range(uint32_t tile_x, uint32_t tile_y, uint64_t offset, uint32_t size) +{ + uint64_t idx = tile_y * nTiles_h(m_parameters) + tile_x; + m_offsets[idx].offset = offset; + m_offsets[idx].size = size; +} + + +template +void writevec(uint8_t* data, size_t& idx, I value, int len) +{ + for (int i = 0; i < len; i++) { + data[idx + i] = static_cast((value >> (len - 1 - i) * 8) & 0xFF); + } + + idx += len; +} + + +std::vector TildHeader::write_offset_table() +{ + uint64_t nTiles = number_of_tiles(m_parameters); + + int offset_entry_size = (m_parameters.offset_field_length + m_parameters.size_field_length) / 8; + uint64_t size = nTiles * offset_entry_size; + + std::vector data; + data.resize(size); + + size_t idx = 0; + + for (const auto& offset: m_offsets) { + writevec(data.data(), idx, offset.offset, m_parameters.offset_field_length / 8); + + if (m_parameters.size_field_length != 0) { + writevec(data.data(), idx, offset.size, m_parameters.size_field_length / 8); + } + } + + assert(idx == data.size()); + + m_header_size = data.size(); + + return data; +} + + +std::string TildHeader::dump() const +{ + std::stringstream sstr; + + sstr << "offsets: "; + + // TODO + + for (const auto& offset: m_offsets) { + sstr << offset.offset << ", size: " << offset.size << "\n"; + } + + return sstr.str(); +} + + +ImageItem_Tiled::ImageItem_Tiled(HeifContext* ctx) + : ImageItem(ctx) +{ +} + + +ImageItem_Tiled::ImageItem_Tiled(HeifContext* ctx, heif_item_id id) + : ImageItem(ctx, id) +{ +} + + +heif_compression_format ImageItem_Tiled::get_compression_format() const +{ + return compression_format_from_fourcc_infe_type(m_tild_header.get_parameters().compression_format_fourcc); +} + + +Error ImageItem_Tiled::on_load_file() +{ + auto heif_file = get_context()->get_heif_file(); + + auto tilC_box = heif_file->get_property(get_id()); + if (!tilC_box) { + return {heif_error_Invalid_input, + heif_suberror_Unspecified, + "Tiled image without 'tilC' property box."}; + } + + auto ispe_box = heif_file->get_property(get_id()); + if (!ispe_box) { + return {heif_error_Invalid_input, + heif_suberror_Unspecified, + "Tiled image without 'ispe' property box."}; + } + + heif_tiled_image_parameters parameters = tilC_box->get_parameters(); + parameters.image_width = ispe_box->get_width(); + parameters.image_height = ispe_box->get_height(); + + if (parameters.image_width == 0 || parameters.image_height == 0) { + return {heif_error_Invalid_input, + heif_suberror_Unspecified, + "'tild' image with zero width or height."}; + } + + if (Error err = m_tild_header.set_parameters(parameters)) { + return err; + } + + m_tile_decoder = Decoder::alloc_for_infe_type(get_context(), get_id(), parameters.compression_format_fourcc); + if (!m_tile_decoder) { + return {heif_error_Unsupported_feature, + heif_suberror_Unsupported_codec, + "'tild' image with unsupported compression format."}; + } + + if (m_preload_offset_table) { + if (Error err = m_tild_header.read_full_offset_table(heif_file, get_id(), get_context()->get_security_limits())) { + return err; + } + } + + return Error::Ok; +} + + +Result> +ImageItem_Tiled::add_new_tiled_item(HeifContext* ctx, const heif_tiled_image_parameters* parameters, + const heif_encoder* encoder) +{ + auto max_tild_tiles = ctx->get_security_limits()->max_number_of_tiles; + if (max_tild_tiles && number_of_tiles(*parameters) > max_tild_tiles) { + return Error{heif_error_Usage_error, + heif_suberror_Security_limit_exceeded, + "Number of tiles exceeds security limit."}; + } + + + // Create 'tild' Item + + auto file = ctx->get_heif_file(); + + heif_item_id tild_id = ctx->get_heif_file()->add_new_image(fourcc("tili")); + auto tild_image = std::make_shared(ctx, tild_id); + tild_image->set_resolution(parameters->image_width, parameters->image_height); + ctx->insert_image_item(tild_id, tild_image); + + // Create tilC box + + auto tilC_box = std::make_shared(); + tilC_box->set_parameters(*parameters); + tilC_box->set_compression_format(encoder->plugin->compression_format); + ctx->get_heif_file()->add_property(tild_id, tilC_box, true); + + // Create header + offset table + + TildHeader tild_header; + tild_header.set_parameters(*parameters); + tild_header.set_compression_format(encoder->plugin->compression_format); + + std::vector header_data = tild_header.write_offset_table(); + + const int construction_method = 0; // 0=mdat 1=idat + file->append_iloc_data(tild_id, header_data, construction_method); + + + if (parameters->image_width > 0xFFFFFFFF || parameters->image_height > 0xFFFFFFFF) { + return {Error(heif_error_Usage_error, heif_suberror_Invalid_image_size, + "'ispe' only supports image sized up to 4294967295 pixels per dimension")}; + } + + // Add ISPE property + file->add_ispe_property(tild_id, + static_cast(parameters->image_width), + static_cast(parameters->image_height), + true); + +#if 0 + // TODO + + // Add PIXI property (copy from first tile) + auto pixi = m_heif_file->get_property(tile_ids[0]); + m_heif_file->add_property(grid_id, pixi, true); +#endif + + tild_image->set_tild_header(tild_header); + tild_image->set_next_tild_position(header_data.size()); + + // Set Brands + //m_heif_file->set_brand(encoder->plugin->compression_format, + // out_grid_image->is_miaf_compatible()); + + return {tild_image}; +} + + +Error ImageItem_Tiled::add_image_tile(uint32_t tile_x, uint32_t tile_y, + const std::shared_ptr& image, + struct heif_encoder* encoder) +{ + auto item = ImageItem::alloc_for_compression_format(get_context(), encoder->plugin->compression_format); + + heif_encoding_options* options = heif_encoding_options_alloc(); // TODO: should this be taken from heif_context_add_tiled_image() ? + + Result> colorConversionResult = item->convert_colorspace_for_encoding(image, encoder, *options); + if (colorConversionResult.error) { + return colorConversionResult.error; + } + + std::shared_ptr colorConvertedImage = colorConversionResult.value; + + Result encodeResult = item->encode_to_bitstream_and_boxes(colorConvertedImage, encoder, *options, heif_image_input_class_normal); // TODO (other than JPEG) + heif_encoding_options_free(options); + + if (encodeResult.error) { + return encodeResult.error; + } + + const int construction_method = 0; // 0=mdat 1=idat + get_file()->append_iloc_data(get_id(), encodeResult.value.bitstream, construction_method); + + auto& header = m_tild_header; + + if (image->get_width() != header.get_parameters().tile_width || + image->get_height() != header.get_parameters().tile_height) { + + std::cout << "tx:" << tile_x << " ty:" << tile_y << "\n"; + std::cout << image->get_width() << " " << header.get_parameters().tile_width << " | " + << image->get_height() << " " << header.get_parameters().tile_height <<"\n"; + + return {heif_error_Usage_error, + heif_suberror_Unspecified, + "Tile image size does not match the specified tile size."}; + } + + uint64_t offset = get_next_tild_position(); + size_t dataSize = encodeResult.value.bitstream.size(); + if (dataSize > 0xFFFFFFFF) { + return {heif_error_Encoding_error, heif_suberror_Unspecified, "Compressed tile size exceeds maximum tile size."}; + } + header.set_tild_tile_range(tile_x, tile_y, offset, static_cast(dataSize)); + set_next_tild_position(offset + encodeResult.value.bitstream.size()); + + std::vector> existing_properties; + Error err = get_file()->get_properties(get_id(), existing_properties); + if (err) { + return err; + } + + for (auto& propertyBox : encodeResult.value.properties) { + if (propertyBox->get_short_type() == fourcc("ispe")) { + continue; + } + + // skip properties that exist already + + bool exists = std::any_of(existing_properties.begin(), + existing_properties.end(), + [&propertyBox](const std::shared_ptr& p) { return p->get_short_type() == propertyBox->get_short_type();}); + if (exists) { + continue; + } + + get_file()->add_property(get_id(), propertyBox, propertyBox->is_essential()); + } + + get_file()->set_brand(encoder->plugin->compression_format, + true); // TODO: out_grid_image->is_miaf_compatible()); + + return Error::Ok; +} + + +void ImageItem_Tiled::process_before_write() +{ + // overwrite offsets + + const int construction_method = 0; // 0=mdat 1=idat + + std::vector header_data = m_tild_header.write_offset_table(); + get_file()->replace_iloc_data(get_id(), 0, header_data, construction_method); +} + + +Result> +ImageItem_Tiled::decode_compressed_image(const struct heif_decoding_options& options, + bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0) const +{ + if (decode_tile_only) { + return decode_grid_tile(options, tile_x0, tile_y0); + } + else { + return Error{heif_error_Unsupported_feature, heif_suberror_Unspecified, + "'tild' images can only be access per tile"}; + } +} + + +Error ImageItem_Tiled::append_compressed_tile_data(std::vector& data, uint32_t tx, uint32_t ty) const +{ + uint32_t idx = (uint32_t) (ty * nTiles_h(m_tild_header.get_parameters()) + tx); + + if (!m_tild_header.is_tile_offset_known(idx)) { + Error err = const_cast(this)->load_tile_offset_entry(idx); + if (err) { + return err; + } + } + + uint64_t offset = m_tild_header.get_tile_offset(idx); + uint64_t size = m_tild_header.get_tile_size(idx); + + Error err = get_file()->append_data_from_iloc(get_id(), data, offset, size); + if (err.error_code) { + return err; + } + + return Error::Ok; +} + + +Result> +ImageItem_Tiled::decode_grid_tile(const heif_decoding_options& options, uint32_t tx, uint32_t ty) const +{ + heif_compression_format format = compression_format_from_fourcc_infe_type( + m_tild_header.get_parameters().compression_format_fourcc); + + // --- get compressed data + + Result> dataResult = read_bitstream_configuration_data_override(get_id(), format); + if (dataResult.error) { + return dataResult.error; + } + + std::vector& data = dataResult.value; + Error err = append_compressed_tile_data(data, tx, ty); + if (err) { + return err; + } + + // --- decode + + DataExtent extent; + extent.m_raw = data; + + m_tile_decoder->set_data_extent(std::move(extent)); + + return m_tile_decoder->decode_single_frame_from_compressed_data(options); +} + + +Error ImageItem_Tiled::load_tile_offset_entry(uint32_t idx) +{ + uint32_t nEntries = mReadChunkSize_bytes / m_tild_header.get_offset_table_entry_size(); + std::pair range = m_tild_header.get_tile_offset_table_range_to_read(idx, nEntries); + + return m_tild_header.read_offset_table_range(get_file(), get_id(), range.first, range.second); +} + + +heif_image_tiling ImageItem_Tiled::get_heif_image_tiling() const +{ + heif_image_tiling tiling{}; + + tiling.num_columns = nTiles_h(m_tild_header.get_parameters()); + tiling.num_rows = nTiles_v(m_tild_header.get_parameters()); + + tiling.tile_width = m_tild_header.get_parameters().tile_width; + tiling.tile_height = m_tild_header.get_parameters().tile_height; + + tiling.image_width = m_tild_header.get_parameters().image_width; + tiling.image_height = m_tild_header.get_parameters().image_height; + tiling.number_of_extra_dimensions = m_tild_header.get_parameters().number_of_extra_dimensions; + for (int i = 0; i < std::min(tiling.number_of_extra_dimensions, uint8_t(8)); i++) { + tiling.extra_dimension_size[i] = m_tild_header.get_parameters().extra_dimensions[i]; + } + + return tiling; +} + + +void ImageItem_Tiled::get_tile_size(uint32_t& w, uint32_t& h) const +{ + w = m_tild_header.get_parameters().tile_width; + h = m_tild_header.get_parameters().tile_height; +} + + +Error ImageItem_Tiled::get_coded_image_colorspace(heif_colorspace* out_colorspace, heif_chroma* out_chroma) const +{ + Error err = m_tile_decoder->get_coded_image_colorspace(out_colorspace, out_chroma); + if (err) { + return err; + } + + postprocess_coded_image_colorspace(out_colorspace, out_chroma); + + return Error::Ok; +} + + +int ImageItem_Tiled::get_luma_bits_per_pixel() const +{ + DataExtent any_tile_extent; + append_compressed_tile_data(any_tile_extent.m_raw, 0,0); // TODO: use tile that is already loaded + m_tile_decoder->set_data_extent(any_tile_extent); + + return m_tile_decoder->get_luma_bits_per_pixel(); +} + +int ImageItem_Tiled::get_chroma_bits_per_pixel() const +{ + DataExtent any_tile_extent; + append_compressed_tile_data(any_tile_extent.m_raw, 0,0); // TODO: use tile that is already loaded + m_tile_decoder->set_data_extent(any_tile_extent); + + return m_tile_decoder->get_chroma_bits_per_pixel(); +} \ No newline at end of file diff --git a/libheif/image-items/tiled.h b/libheif/image-items/tiled.h new file mode 100644 index 0000000000..9af358b132 --- /dev/null +++ b/libheif/image-items/tiled.h @@ -0,0 +1,207 @@ +/* + * HEIF codec. + * Copyright (c) 2024 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_TILED_H +#define LIBHEIF_TILED_H + + +#include "image_item.h" +#include "box.h" +#include +#include +#include +#include +#include "libheif/heif_experimental.h" + + +uint64_t number_of_tiles(const heif_tiled_image_parameters& params); + +uint32_t nTiles_h(const heif_tiled_image_parameters& params); + +uint32_t nTiles_v(const heif_tiled_image_parameters& params); + + +class Box_tilC : public FullBox +{ + /* + * Flags: + * bit 0-1 - number of bits for offsets (0: 32, 1: 40, 2: 48, 3: 64) + * bit 2-3 - number of bits for tile size (0: 0, 1: 24; 2: 32, 3: 64) + * bit 4 - sequential ordering hint + * bit 5 - use 64 bit dimensions (currently unused because ispe is limited to 32 bit) + */ +public: + Box_tilC() + { + set_short_type(fourcc("tilC")); + } + + bool is_essential() const override { return true; } + + void derive_box_version() override; + + void set_parameters(const heif_tiled_image_parameters& params) { m_parameters = params; } + + void set_compression_format(heif_compression_format format) { m_parameters.compression_format_fourcc = ImageItem::compression_format_to_fourcc_infe_type(format); } + + const heif_tiled_image_parameters& get_parameters() const { return m_parameters; } + + Error write(StreamWriter& writer) const override; + + std::string dump(Indent&) const override; + +protected: + Error parse(BitstreamRange& range, const heif_security_limits* limits) override; + +private: + heif_tiled_image_parameters m_parameters; +}; + + +#define TILD_OFFSET_NOT_AVAILABLE 0 +#define TILD_OFFSET_SEE_LOWER_RESOLUTION_LAYER 1 +#define TILD_OFFSET_NOT_LOADED 10 + +class TildHeader +{ +public: + Error set_parameters(const heif_tiled_image_parameters& params); + + const heif_tiled_image_parameters& get_parameters() const { return m_parameters; } + + void set_compression_format(heif_compression_format format) { m_parameters.compression_format_fourcc = ImageItem::compression_format_to_fourcc_infe_type(format); } + + Error read_full_offset_table(const std::shared_ptr& file, heif_item_id tild_id, const heif_security_limits* limits); + + Error read_offset_table_range(const std::shared_ptr& file, heif_item_id tild_id, + uint64_t start, uint64_t end); + + std::vector write_offset_table(); + + std::string dump() const; + + void set_tild_tile_range(uint32_t tile_x, uint32_t tile_y, uint64_t offset, uint32_t size); + + size_t get_header_size() const; + + uint64_t get_tile_offset(uint32_t idx) const { return m_offsets[idx].offset; } + + uint32_t get_tile_size(uint32_t idx) const { return m_offsets[idx].size; } + + bool is_tile_offset_known(uint32_t idx) const { return m_offsets[idx].offset != TILD_OFFSET_NOT_LOADED; } + + uint32_t get_offset_table_entry_size() const; + + // Assuming that we have to read offset table 'idx', but we'd like to read more entries at once to reduce + // the load of small network transfers (preferred: 'nEntries'). Return a range of indices to read. + // This may be less than 'nEntries'. The returned range is [start, end) + [[nodiscard]] std::pair get_tile_offset_table_range_to_read(uint32_t idx, uint32_t nEntries) const; + +private: + heif_tiled_image_parameters m_parameters; + + struct TileOffset { + uint64_t offset = TILD_OFFSET_NOT_LOADED; + uint32_t size = 0; + }; + + // TODO uint64_t m_start_of_offset_table_in_file = 0; + std::vector m_offsets; + + // TODO size_t m_offset_table_start = 0; // start of offset table (= number of bytes in header) + size_t m_header_size = 0; // including offset table +}; + + +class ImageItem_Tiled : public ImageItem +{ +public: + ImageItem_Tiled(HeifContext* ctx, heif_item_id id); + + ImageItem_Tiled(HeifContext* ctx); + + uint32_t get_infe_type() const override { return fourcc("tili"); } + + // TODO: nclx depends on contained format + // const heif_color_profile_nclx* get_forced_output_nclx() const override { return nullptr; } + + heif_compression_format get_compression_format() const override; + + static Result> add_new_tiled_item(HeifContext* ctx, const heif_tiled_image_parameters* parameters, + const heif_encoder* encoder); + + Error add_image_tile(uint32_t tile_x, uint32_t tile_y, + const std::shared_ptr& image, + struct heif_encoder* encoder); + + + Error on_load_file() override; + + void process_before_write() override; + + Error get_coded_image_colorspace(heif_colorspace* out_colorspace, heif_chroma* out_chroma) const override; + + int get_luma_bits_per_pixel() const override; + + int get_chroma_bits_per_pixel() const override; + + Result encode(const std::shared_ptr& image, + struct heif_encoder* encoder, + const struct heif_encoding_options& options, + enum heif_image_input_class input_class) override { + return Error{heif_error_Unsupported_feature, + heif_suberror_Unspecified, "Cannot encode image to 'tild'"}; + } + + Result> decode_compressed_image(const struct heif_decoding_options& options, + bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0) const override; + + // --- tild + + void set_tild_header(const TildHeader& header) { m_tild_header = header; } + + TildHeader& get_tild_header() { return m_tild_header; } + + uint64_t get_next_tild_position() const { return m_next_tild_position; } + + void set_next_tild_position(uint64_t pos) { m_next_tild_position = pos; } + + heif_image_tiling get_heif_image_tiling() const override; + + void get_tile_size(uint32_t& w, uint32_t& h) const override; + +private: + TildHeader m_tild_header; + uint64_t m_next_tild_position = 0; + + uint32_t mReadChunkSize_bytes = 64*1024; // 64 kiB + bool m_preload_offset_table = false; + + std::shared_ptr m_tile_decoder; + + Result> decode_grid_tile(const heif_decoding_options& options, uint32_t tx, uint32_t ty) const; + + Error load_tile_offset_entry(uint32_t idx); + + Error append_compressed_tile_data(std::vector& data, uint32_t tx, uint32_t ty) const; +}; + + +#endif //LIBHEIF_TILED_H diff --git a/libheif/image-items/unc_image.cc b/libheif/image-items/unc_image.cc new file mode 100644 index 0000000000..255f1e7530 --- /dev/null +++ b/libheif/image-items/unc_image.cc @@ -0,0 +1,522 @@ +/* + * HEIF codec. + * Copyright (c) 2023 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 +#include +#include +#include +#include +#include + +#include "common_utils.h" +#include "context.h" +#include "compression.h" +#include "error.h" +#include "libheif/heif.h" +#include "codecs/uncompressed/unc_types.h" +#include "codecs/uncompressed/unc_boxes.h" +#include "unc_image.h" +#include "codecs/uncompressed/unc_dec.h" +#include "codecs/uncompressed/unc_codec.h" +#include "image_item.h" + + + +static void maybe_make_minimised_uncC(std::shared_ptr& uncC, const std::shared_ptr& image) +{ + uncC->set_version(0); + if (image->get_colorspace() != heif_colorspace_RGB) { + return; + } + if (!((image->get_chroma_format() == heif_chroma_interleaved_RGB) || (image->get_chroma_format() == heif_chroma_interleaved_RGBA))) { + return; + } + if (image->get_bits_per_pixel(heif_channel_interleaved) != 8) { + return; + } + if (image->get_chroma_format() == heif_chroma_interleaved_RGBA) { + uncC->set_profile(fourcc("rgba")); + } else { + uncC->set_profile(fourcc("rgb3")); + } + uncC->set_version(1); +} + + +Result> ImageItem_uncompressed::decode_compressed_image(const struct heif_decoding_options& options, + bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0) const +{ + std::shared_ptr img; + + std::vector data; + + Error err; + + if (decode_tile_only) { + err = UncompressedImageCodec::decode_uncompressed_image_tile(get_context(), + get_id(), + img, + tile_x0, tile_y0); + } + else { + err = UncompressedImageCodec::decode_uncompressed_image(get_context(), + get_id(), + img); + } + + if (err) { + return err; + } + else { + return img; + } +} + + +struct unciHeaders +{ + std::shared_ptr uncC; + std::shared_ptr cmpd; +}; + + +static Result generate_headers(const std::shared_ptr& src_image, + const heif_unci_image_parameters* parameters, + const struct heif_encoding_options* options) +{ + unciHeaders headers; + + bool uses_tiles = (parameters->tile_width != parameters->image_width || + parameters->tile_height != parameters->image_height); + + std::shared_ptr uncC = std::make_shared(); + if (options && options->prefer_uncC_short_form && !uses_tiles) { + maybe_make_minimised_uncC(uncC, src_image); + } + + if (uncC->get_version() == 1) { + headers.uncC = uncC; + } else { + std::shared_ptr cmpd = std::make_shared(); + + Error error = fill_cmpd_and_uncC(cmpd, uncC, src_image, parameters); + if (error) { + return error; + } + + headers.cmpd = cmpd; + headers.uncC = uncC; + } + + return headers; +} + + +Result> encode_image_tile(const std::shared_ptr& src_image) +{ + std::vector data; + + if (src_image->get_colorspace() == heif_colorspace_YCbCr) + { + uint64_t offset = 0; + for (heif_channel channel : {heif_channel_Y, heif_channel_Cb, heif_channel_Cr}) + { + uint32_t src_stride; + uint32_t src_width = src_image->get_width(channel); + uint32_t src_height = src_image->get_height(channel); + const uint8_t* src_data = src_image->get_plane(channel, &src_stride); + uint64_t out_size = src_width * src_height; + data.resize(data.size() + out_size); + for (uint32_t y = 0; y < src_height; y++) { + memcpy(data.data() + offset + y * src_width, src_data + src_stride * y, src_width); + } + offset += out_size; + } + + return data; + } + else if (src_image->get_colorspace() == heif_colorspace_RGB) + { + if (src_image->get_chroma_format() == heif_chroma_444) + { + uint64_t offset = 0; + std::vector channels = {heif_channel_R, heif_channel_G, heif_channel_B}; + if (src_image->has_channel(heif_channel_Alpha)) + { + channels.push_back(heif_channel_Alpha); + } + for (heif_channel channel : channels) + { + uint32_t src_stride; + const uint8_t* src_data = src_image->get_plane(channel, &src_stride); + uint64_t out_size = src_image->get_height() * src_image->get_width(); + + data.resize(data.size() + out_size); + for (uint32_t y = 0; y < src_image->get_height(); y++) { + memcpy(data.data() + offset + y * src_image->get_width(), src_data + y * src_stride, src_image->get_width()); + } + + offset += out_size; + } + + return data; + } + else if ((src_image->get_chroma_format() == heif_chroma_interleaved_RGB) || + (src_image->get_chroma_format() == heif_chroma_interleaved_RGBA) || + (src_image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_BE) || + (src_image->get_chroma_format() == heif_chroma_interleaved_RRGGBB_LE) || + (src_image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_BE) || + (src_image->get_chroma_format() == heif_chroma_interleaved_RRGGBBAA_LE)) + { + int bytes_per_pixel = 0; + switch (src_image->get_chroma_format()) { + case heif_chroma_interleaved_RGB: + bytes_per_pixel=3; + break; + case heif_chroma_interleaved_RGBA: + bytes_per_pixel=4; + break; + case heif_chroma_interleaved_RRGGBB_BE: + case heif_chroma_interleaved_RRGGBB_LE: + bytes_per_pixel=6; + break; + case heif_chroma_interleaved_RRGGBBAA_BE: + case heif_chroma_interleaved_RRGGBBAA_LE: + bytes_per_pixel=8; + break; + default: + assert(false); + } + + uint32_t src_stride; + const uint8_t* src_data = src_image->get_plane(heif_channel_interleaved, &src_stride); + uint64_t out_size = src_image->get_height() * src_image->get_width() * bytes_per_pixel; + data.resize(out_size); + for (uint32_t y = 0; y < src_image->get_height(); y++) { + memcpy(data.data() + y * src_image->get_width() * bytes_per_pixel, src_data + src_stride * y, src_image->get_width() * bytes_per_pixel); + } + + return data; + } + else + { + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + "Unsupported RGB chroma"); + } + } + else if (src_image->get_colorspace() == heif_colorspace_monochrome) + { + uint64_t offset = 0; + std::vector channels; + if (src_image->has_channel(heif_channel_Alpha)) + { + channels = {heif_channel_Y, heif_channel_Alpha}; + } + else + { + channels = {heif_channel_Y}; + } + for (heif_channel channel : channels) + { + uint32_t src_stride; + const uint8_t* src_data = src_image->get_plane(channel, &src_stride); + uint64_t out_size = src_image->get_height() * src_stride; + data.resize(data.size() + out_size); + memcpy(data.data() + offset, src_data, out_size); + offset += out_size; + } + + return data; + } + else + { + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + "Unsupported colourspace"); + } + +} + + +Result ImageItem_uncompressed::encode(const std::shared_ptr& src_image, + struct heif_encoder* encoder, + const struct heif_encoding_options& options, + enum heif_image_input_class input_class) +{ + heif_unci_image_parameters parameters{}; + parameters.image_width = src_image->get_width(); + parameters.image_height = src_image->get_height(); + parameters.tile_width = parameters.image_width; + parameters.tile_height = parameters.image_height; + + + // --- generate configuration property boxes + + Result genHeadersResult = generate_headers(src_image, ¶meters, &options); + if (genHeadersResult.error) { + return genHeadersResult.error; + } + + const unciHeaders& headers = *genHeadersResult; + + CodedImageData codedImageData; + if (headers.uncC) { + codedImageData.properties.push_back(headers.uncC); + } + if (headers.cmpd) { + codedImageData.properties.push_back(headers.cmpd); + } + + + // --- encode image + + Result> codedBitstreamResult = encode_image_tile(src_image); + if (codedBitstreamResult.error) { + return codedBitstreamResult.error; + } + + codedImageData.bitstream = *codedBitstreamResult; + + return codedImageData; +} + + +Result> ImageItem_uncompressed::add_unci_item(HeifContext* ctx, + const heif_unci_image_parameters* parameters, + const struct heif_encoding_options* encoding_options, + const std::shared_ptr& prototype) +{ + // Check input parameters + + if (parameters->image_width % parameters->tile_width != 0 || + parameters->image_height % parameters->tile_height != 0) { + return Error{heif_error_Invalid_input, + heif_suberror_Invalid_parameter_value, + "ISO 23001-17 image size must be an integer multiple of the tile size."}; + } + + // Create 'unci' Item + + auto file = ctx->get_heif_file(); + + heif_item_id unci_id = ctx->get_heif_file()->add_new_image(fourcc("unci")); + auto unci_image = std::make_shared(ctx, unci_id); + unci_image->set_resolution(parameters->image_width, parameters->image_height); + ctx->insert_image_item(unci_id, unci_image); + + + // Generate headers + + Result genHeadersResult = generate_headers(prototype, parameters, encoding_options); + if (genHeadersResult.error) { + return genHeadersResult.error; + } + + const unciHeaders& headers = *genHeadersResult; + + if (headers.uncC) { + file->add_property(unci_id, headers.uncC, true); + } + + if (headers.cmpd) { + file->add_property(unci_id, headers.cmpd, true); + } + + // Add `ispe` property + + file->add_ispe_property(unci_id, + static_cast(parameters->image_width), + static_cast(parameters->image_height), + true); + + if (parameters->compression != heif_metadata_compression_off) { + auto icef = std::make_shared(); + auto cmpC = std::make_shared(); + cmpC->set_compressed_unit_type(heif_cmpC_compressed_unit_type_image_tile); + + if (parameters->compression == heif_metadata_compression_deflate) { + cmpC->set_compression_type(fourcc("defl")); + } + else if (parameters->compression == heif_metadata_compression_zlib) { + cmpC->set_compression_type(fourcc("zlib")); + } +#if HAVE_BROTLI + else if (parameters->compression == heif_metadata_compression_brotli) { + cmpC->set_compression_type(fourcc("brot")); + } +#endif + else { + assert(false); + } + + file->add_property(unci_id, cmpC, true); + file->add_property_without_deduplication(unci_id, icef, true); // icef is empty. A normal add_property() would lead to a wrong deduplication. + } + + // Create empty image. If we use compression, we append the data piece by piece. + + if (parameters->compression == heif_metadata_compression_off) { + uint64_t tile_size = headers.uncC->compute_tile_data_size_bytes(parameters->image_width / headers.uncC->get_number_of_tile_columns(), + parameters->image_height / headers.uncC->get_number_of_tile_rows()); + + std::vector dummydata; + dummydata.resize(tile_size); + + uint32_t nTiles = (parameters->image_width / parameters->tile_width) * (parameters->image_height / parameters->tile_height); + + for (uint64_t i = 0; i < nTiles; i++) { + const int construction_method = 0; // 0=mdat 1=idat + file->append_iloc_data(unci_id, dummydata, construction_method); + } + } + + // Set Brands + ctx->get_heif_file()->set_brand(heif_compression_uncompressed, unci_image->is_miaf_compatible()); + + return {unci_image}; +} + + +Error ImageItem_uncompressed::add_image_tile(uint32_t tile_x, uint32_t tile_y, const std::shared_ptr& image) +{ + std::shared_ptr uncC = get_file()->get_property(get_id()); + assert(uncC); + + uint32_t tile_width = image->get_width(); + uint32_t tile_height = image->get_height(); + + uint32_t tile_idx = tile_y * uncC->get_number_of_tile_columns() + tile_x; + + Result> codedBitstreamResult = encode_image_tile(image); + if (codedBitstreamResult.error) { + return codedBitstreamResult.error; + } + + std::shared_ptr cmpC = get_file()->get_property(get_id()); + std::shared_ptr icef = get_file()->get_property(get_id()); + + if (!icef || !cmpC) { + assert(!icef); + assert(!cmpC); + + // uncompressed + + uint64_t tile_data_size = uncC->compute_tile_data_size_bytes(tile_width, tile_height); + + get_file()->replace_iloc_data(get_id(), tile_idx * tile_data_size, *codedBitstreamResult, 0); + } + else { + std::vector compressed_data; + const std::vector& raw_data = codedBitstreamResult.value; + + uint32_t compr = cmpC->get_compression_type(); + switch (compr) { + case fourcc("defl"): + compressed_data = compress_deflate(raw_data.data(), raw_data.size()); + break; + case fourcc("zlib"): + compressed_data = compress_zlib(raw_data.data(), raw_data.size()); + break; +#if HAVE_BROTLI + case fourcc("brot"): + compressed_data = compress_brotli(raw_data.data(), raw_data.size()); + break; +#endif + default: + assert(false); + break; + } + + get_file()->append_iloc_data(get_id(), compressed_data, 0); + + Box_icef::CompressedUnitInfo unit_info; + unit_info.unit_offset = m_next_tile_write_pos; + unit_info.unit_size = compressed_data.size(); + icef->set_component(tile_idx, unit_info); + + m_next_tile_write_pos += compressed_data.size(); + } + + return Error::Ok; +} + + +void ImageItem_uncompressed::get_tile_size(uint32_t& w, uint32_t& h) const +{ + auto ispe = get_file()->get_property(get_id()); + auto uncC = get_file()->get_property(get_id()); + + if (!ispe || !uncC) { + w=h=0; + } + + w = ispe->get_width() / uncC->get_number_of_tile_columns(); + h = ispe->get_height() / uncC->get_number_of_tile_rows(); +} + + +heif_image_tiling ImageItem_uncompressed::get_heif_image_tiling() const +{ + heif_image_tiling tiling{}; + + auto ispe = get_file()->get_property(get_id()); + auto uncC = get_file()->get_property(get_id()); + assert(ispe && uncC); + + tiling.num_columns = uncC->get_number_of_tile_columns(); + tiling.num_rows = uncC->get_number_of_tile_rows(); + + tiling.tile_width = ispe->get_width() / tiling.num_columns; + tiling.tile_height = ispe->get_height() / tiling.num_rows; + + tiling.image_width = ispe->get_width(); + tiling.image_height = ispe->get_height(); + tiling.number_of_extra_dimensions = 0; + + return tiling; +} + +std::shared_ptr ImageItem_uncompressed::get_decoder() const +{ + return m_decoder; +} + +Error ImageItem_uncompressed::on_load_file() +{ + std::shared_ptr cmpd = get_file()->get_property(get_id()); + std::shared_ptr uncC = get_file()->get_property(get_id()); + + if (!uncC) { + return Error{heif_error_Invalid_input, + heif_suberror_Unspecified, + "No 'uncC' box found."}; + } + + m_decoder = std::make_shared(uncC, cmpd); + + DataExtent extent; + extent.set_from_image_item(get_context()->get_heif_file(), get_id()); + + m_decoder->set_data_extent(extent); + + return Error::Ok; +} diff --git a/libheif/image-items/unc_image.h b/libheif/image-items/unc_image.h new file mode 100644 index 0000000000..e1453ad2f8 --- /dev/null +++ b/libheif/image-items/unc_image.h @@ -0,0 +1,96 @@ +/* + * HEIF codec. + * Copyright (c) 2023 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_UNC_IMAGE_H +#define LIBHEIF_UNC_IMAGE_H + +#include "pixelimage.h" +#include "file.h" +#include "context.h" + +#include +#include +#include +#include + +class HeifContext; + + +class ImageItem_uncompressed : public ImageItem +{ +public: + ImageItem_uncompressed(HeifContext* ctx, heif_item_id id) : ImageItem(ctx, id) {} + + ImageItem_uncompressed(HeifContext* ctx) : ImageItem(ctx) {} + + uint32_t get_infe_type() const override { return fourcc("unci"); } + + heif_compression_format get_compression_format() const override { return heif_compression_uncompressed; } + + // Instead of storing alpha in a separate unci, this is put into the main image item + const char* get_auxC_alpha_channel_type() const override { return nullptr; } + + const heif_color_profile_nclx* get_forced_output_nclx() const override { return nullptr; } + + bool is_ispe_essential() const override { return true; } + + void get_tile_size(uint32_t& w, uint32_t& h) const override; + + // Code from encode_uncompressed_image() has been moved to here. + + Result> decode_compressed_image(const struct heif_decoding_options& options, + bool decode_tile_only, uint32_t tile_x0, uint32_t tile_y0) const override; + + heif_image_tiling get_heif_image_tiling() const override; + + Error on_load_file() override; + +public: + + // --- encoding + + Result encode(const std::shared_ptr& image, + struct heif_encoder* encoder, + const struct heif_encoding_options& options, + enum heif_image_input_class input_class) override; + + static Result> add_unci_item(HeifContext* ctx, + const heif_unci_image_parameters* parameters, + const struct heif_encoding_options* encoding_options, + const std::shared_ptr& prototype); + + Error add_image_tile(uint32_t tile_x, uint32_t tile_y, const std::shared_ptr& image); + +protected: + std::shared_ptr get_decoder() const override; + +private: + std::shared_ptr m_decoder; + /* + Result generate_headers(const std::shared_ptr& src_image, + const heif_unci_image_parameters* parameters, + const struct heif_encoding_options* options); + */ + + uint64_t m_next_tile_write_pos = 0; +}; + +#endif //LIBHEIF_UNC_IMAGE_H diff --git a/libheif/image-items/vvc.cc b/libheif/image-items/vvc.cc new file mode 100644 index 0000000000..2e3bb9236f --- /dev/null +++ b/libheif/image-items/vvc.cc @@ -0,0 +1,140 @@ +/* + * HEIF VVC codec. + * Copyright (c) 2023 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 "vvc.h" +#include "codecs/vvc_dec.h" +#include "codecs/vvc_boxes.h" +#include +#include +#include +#include "api/libheif/api_structs.h" + + +Result ImageItem_VVC::encode(const std::shared_ptr& image, + struct heif_encoder* encoder, + const struct heif_encoding_options& options, + enum heif_image_input_class input_class) +{ + CodedImageData codedImage; + + auto vvcC = std::make_shared(); + codedImage.properties.push_back(vvcC); + + + heif_image c_api_image; + c_api_image.image = image; + + struct heif_error err = encoder->plugin->encode_image(encoder->encoder, &c_api_image, input_class); + if (err.code) { + return Error(err.code, + err.subcode, + err.message); + } + + int encoded_width = 0; + int encoded_height = 0; + + for (;;) { + uint8_t* data; + int size; + + encoder->plugin->get_compressed_data(encoder->encoder, &data, &size, NULL); + + if (data == NULL) { + break; + } + + + const uint8_t NAL_SPS = 15; + + uint8_t nal_type = 0; + if (size>=2) { + nal_type = (data[1] >> 3) & 0x1F; + } + + if (nal_type == NAL_SPS) { + Box_vvcC::configuration config; + + parse_sps_for_vvcC_configuration(data, size, &config, &encoded_width, &encoded_height); + + vvcC->set_configuration(config); + } + + switch (nal_type) { + case 14: // VPS + case 15: // SPS + case 16: // PPS + vvcC->append_nal_data(data, size); + break; + + default: + codedImage.append_with_4bytes_size(data, size); + } + } + + return codedImage; +} + + +Result> ImageItem_VVC::read_bitstream_configuration_data(heif_item_id itemId) const +{ + // --- get codec configuration + + std::shared_ptr vvcC_box = get_file()->get_property(itemId); + if (!vvcC_box) + { + assert(false); + return Error(heif_error_Invalid_input, + heif_suberror_No_vvcC_box); + } + + std::vector data; + if (!vvcC_box->get_headers(&data)) + { + return Error(heif_error_Invalid_input, + heif_suberror_No_item_data); + } + + return data; +} + + +std::shared_ptr ImageItem_VVC::get_decoder() const +{ + return m_decoder; +} + +Error ImageItem_VVC::on_load_file() +{ + auto vvcC_box = get_file()->get_property(get_id()); + if (!vvcC_box) { + return Error{heif_error_Invalid_input, + heif_suberror_No_av1C_box}; + } + + m_decoder = std::make_shared(vvcC_box); + + DataExtent extent; + extent.set_from_image_item(get_context()->get_heif_file(), get_id()); + + m_decoder->set_data_extent(extent); + + return Error::Ok; +} diff --git a/libheif/image-items/vvc.h b/libheif/image-items/vvc.h new file mode 100644 index 0000000000..384e7e9507 --- /dev/null +++ b/libheif/image-items/vvc.h @@ -0,0 +1,62 @@ +/* + * HEIF VVC codec. + * Copyright (c) 2023 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_VVC_H +#define LIBHEIF_VVC_H + +#include "box.h" +#include +#include +#include "image_item.h" +#include + + +class ImageItem_VVC : public ImageItem +{ +public: + ImageItem_VVC(HeifContext* ctx, heif_item_id id) : ImageItem(ctx, id) {} + + ImageItem_VVC(HeifContext* ctx) : ImageItem(ctx) {} + + uint32_t get_infe_type() const override { return fourcc("vvc1"); } + + const char* get_auxC_alpha_channel_type() const override { return "urn:mpeg:mpegB:cicp:systems:auxiliary:alpha"; } + + const heif_color_profile_nclx* get_forced_output_nclx() const override { return nullptr; } + + heif_compression_format get_compression_format() const override { return heif_compression_VVC; } + + Result encode(const std::shared_ptr& image, + struct heif_encoder* encoder, + const struct heif_encoding_options& options, + enum heif_image_input_class input_class) override; + + Error on_load_file() override; + +protected: + std::shared_ptr get_decoder() const override; + + Result> read_bitstream_configuration_data(heif_item_id itemId) const override; + +private: + std::shared_ptr m_decoder; +}; + +#endif // LIBHEIF_VVC_H diff --git a/libheif/nclx.cc b/libheif/nclx.cc index 95339f4ed5..92f0483393 100644 --- a/libheif/nclx.cc +++ b/libheif/nclx.cc @@ -20,10 +20,9 @@ #include "nclx.h" -#include "security_limits.h" +#include "libheif/heif_experimental.h" #include -#include #include #include #include @@ -221,7 +220,7 @@ Error color_profile_nclx::parse(BitstreamRange& range) { StreamReader::grow_status status; status = range.wait_for_available_bytes(7); - if (status != StreamReader::size_reached) { + if (status != StreamReader::grow_status::size_reached) { // TODO: return recoverable error at timeout return Error(heif_error_Invalid_input, heif_suberror_End_of_data); @@ -359,7 +358,7 @@ void color_profile_nclx::replace_undefined_values_with_sRGB_defaults() } -Error Box_colr::parse(BitstreamRange& range) +Error Box_colr::parse(BitstreamRange& range, const heif_security_limits* limits) { StreamReader::grow_status status; uint32_t colour_type = range.read32(); @@ -379,14 +378,14 @@ Error Box_colr::parse(BitstreamRange& range) } uint64_t profile_size_64 = get_box_size() - get_header_size() - 4; - if (profile_size_64 > MAX_COLOR_PROFILE_SIZE) { + if (profile_size_64 > limits->max_color_profile_size) { return Error(heif_error_Invalid_input, heif_suberror_Security_limit_exceeded, "Color profile exceeds maximum supported size"); } size_t profile_size = static_cast(profile_size_64); status = range.wait_for_available_bytes(profile_size); - if (status != StreamReader::size_reached) { + if (status != StreamReader::grow_status::size_reached) { // TODO: return recoverable error at timeout return Error(heif_error_Invalid_input, heif_suberror_End_of_data); @@ -414,7 +413,7 @@ std::string Box_colr::dump(Indent& indent) const sstr << Box::dump(indent); if (m_color_profile) { - sstr << indent << "colour_type: " << to_fourcc(get_color_profile_type()) << "\n"; + sstr << indent << "colour_type: " << fourcc_to_string(get_color_profile_type()) << "\n"; sstr << m_color_profile->dump(indent); } else { diff --git a/libheif/nclx.h b/libheif/nclx.h index 5779e440e3..3938479fcb 100644 --- a/libheif/nclx.h +++ b/libheif/nclx.h @@ -189,7 +189,7 @@ class Box_colr : public Box Error write(StreamWriter& writer) const override; protected: - Error parse(BitstreamRange& range) override; + Error parse(BitstreamRange& range, const heif_security_limits* limits) override; private: std::shared_ptr m_color_profile; diff --git a/libheif/pixelimage.cc b/libheif/pixelimage.cc index 9fd69366d7..f0dc302564 100644 --- a/libheif/pixelimage.cc +++ b/libheif/pixelimage.cc @@ -21,10 +21,14 @@ #include "pixelimage.h" #include "common_utils.h" +#include "security_limits.h" #include #include #include +#include +#include +#include heif_chroma chroma_from_subsampling(int h, int v) @@ -45,6 +49,47 @@ heif_chroma chroma_from_subsampling(int h, int v) } +uint32_t chroma_width(uint32_t w, heif_chroma chroma) +{ + switch (chroma) { + case heif_chroma_420: + case heif_chroma_422: + return (w+1)/2; + default: + return w; + } +} + +uint32_t chroma_height(uint32_t h, heif_chroma chroma) +{ + switch (chroma) { + case heif_chroma_420: + return (h+1)/2; + default: + return h; + } +} + +uint32_t channel_width(uint32_t w, heif_chroma chroma, heif_channel channel) +{ + if (channel == heif_channel_Cb || channel == heif_channel_Cr) { + return chroma_width(w, chroma); + } + else { + return w; + } +} + +uint32_t channel_height(uint32_t h, heif_chroma chroma, heif_channel channel) +{ + if (channel == heif_channel_Cb || channel == heif_channel_Cr) { + return chroma_height(h, chroma); + } + else { + return h; + } +} + HeifPixelImage::~HeifPixelImage() { for (auto& iter : m_planes) { @@ -79,8 +124,8 @@ int num_interleaved_pixels_per_plane(heif_chroma chroma) } -bool is_integer_multiple_of_chroma_size(int width, - int height, +bool is_integer_multiple_of_chroma_size(uint32_t width, + uint32_t height, heif_chroma chroma) { switch (chroma) { @@ -116,13 +161,16 @@ std::vector get_valid_chroma_values_for_colorspace(heif_colorspace case heif_colorspace_monochrome: return {heif_chroma_monochrome}; + case heif_colorspace_nonvisual: + return {heif_chroma_undefined}; + default: return {}; } } -void HeifPixelImage::create(int width, int height, heif_colorspace colorspace, heif_chroma chroma) +void HeifPixelImage::create(uint32_t width, uint32_t height, heif_colorspace colorspace, heif_chroma chroma) { m_width = width; m_height = height; @@ -141,10 +189,37 @@ static uint32_t rounded_size(uint32_t s) return s; } -bool HeifPixelImage::add_plane(heif_channel channel, int width, int height, int bit_depth) +bool HeifPixelImage::add_plane(heif_channel channel, uint32_t width, uint32_t height, int bit_depth) +{ + assert(!has_channel(channel)); + + ImagePlane plane; + int num_interleaved_pixels = num_interleaved_pixels_per_plane(m_chroma); + + // for backwards compatibility, allow for 24/32 bits for RGB/RGBA interleaved chromas + + if (m_chroma == heif_chroma_interleaved_RGB && bit_depth == 24) { + bit_depth = 8; + } + + if (m_chroma == heif_chroma_interleaved_RGBA && bit_depth == 32) { + bit_depth = 8; + } + + if (plane.alloc(width, height, heif_channel_datatype_unsigned_integer, bit_depth, num_interleaved_pixels)) { + m_planes.insert(std::make_pair(channel, plane)); + return true; + } + else { + return false; + } +} + + +bool HeifPixelImage::add_channel(heif_channel channel, uint32_t width, uint32_t height, heif_channel_datatype datatype, int bit_depth) { ImagePlane plane; - if (plane.alloc(width, height, bit_depth, m_chroma)) { + if (plane.alloc(width, height, datatype, bit_depth, 1)) { m_planes.insert(std::make_pair(channel, plane)); return true; } @@ -154,14 +229,15 @@ bool HeifPixelImage::add_plane(heif_channel channel, int width, int height, int } -bool HeifPixelImage::ImagePlane::alloc(int width, int height, int bit_depth, heif_chroma chroma) +bool HeifPixelImage::ImagePlane::alloc(uint32_t width, uint32_t height, heif_channel_datatype datatype, int bit_depth, + int num_interleaved_components) // heif_chroma chroma) { assert(width >= 0); assert(height >= 0); assert(bit_depth >= 1); - assert(bit_depth <= 32); + assert(bit_depth <= 128); - // use 16 byte alignment + // use 16 byte alignment (enough for 128 bit data-types). Every row is an integer number of data-elements. uint16_t alignment = 16; // must be power of two m_width = width; @@ -170,38 +246,37 @@ bool HeifPixelImage::ImagePlane::alloc(int width, int height, int bit_depth, hei m_mem_width = rounded_size(width); m_mem_height = rounded_size(height); - // for backwards compatibility, allow for 24/32 bits for RGB/RGBA interleaved chromas + assert(num_interleaved_components > 0 && num_interleaved_components <= 255); - if (chroma == heif_chroma_interleaved_RGB && bit_depth == 24) { - bit_depth = 8; - } - - if (chroma == heif_chroma_interleaved_RGBA && bit_depth == 32) { - bit_depth = 8; - } - - assert(m_bit_depth <= 16); m_bit_depth = static_cast(bit_depth); + m_num_interleaved_components = static_cast(num_interleaved_components); + m_datatype = datatype; - int bytes_per_component = (m_bit_depth + 7) / 8; - int bytes_per_pixel = num_interleaved_pixels_per_plane(chroma) * bytes_per_component; + int bytes_per_component = get_bytes_per_pixel(); + int bytes_per_pixel = num_interleaved_components * bytes_per_component; stride = m_mem_width * bytes_per_pixel; stride = (stride + alignment - 1U) & ~(alignment - 1U); + if ((heif_get_global_security_limits()->max_memory_block_size - (alignment + 1)) / stride < m_mem_height) { + return false; + } + try { - allocated_mem = new uint8_t[m_mem_height * stride + alignment - 1]; - mem = allocated_mem; + allocated_mem = new uint8_t[static_cast(m_mem_height) * stride + alignment - 1]; + uint8_t* mem_8 = allocated_mem; // shift beginning of image data to aligned memory position - auto mem_start_addr = (uint64_t) mem; + auto mem_start_addr = (uint64_t) mem_8; auto mem_start_offset = (mem_start_addr & (alignment - 1U)); if (mem_start_offset != 0) { - mem += alignment - mem_start_offset; + mem_8 += alignment - mem_start_offset; } + mem = mem_8; + return true; } catch (const std::bad_alloc& excpt) { @@ -210,32 +285,34 @@ bool HeifPixelImage::ImagePlane::alloc(int width, int height, int bit_depth, hei } -bool HeifPixelImage::extend_padding_to_size(int width, int height) +bool HeifPixelImage::extend_padding_to_size(uint32_t width, uint32_t height, bool adjust_size) { for (auto& planeIter : m_planes) { auto* plane = &planeIter.second; - int subsampled_width, subsampled_height; + uint32_t subsampled_width, subsampled_height; get_subsampled_size(width, height, planeIter.first, m_chroma, &subsampled_width, &subsampled_height); - int old_width = plane->m_width; - int old_height = plane->m_height; + uint32_t old_width = plane->m_width; + uint32_t old_height = plane->m_height; + + int bytes_per_pixel = get_storage_bits_per_pixel(planeIter.first) / 8; if (plane->m_mem_width < subsampled_width || plane->m_mem_height < subsampled_height) { ImagePlane newPlane; - if (!newPlane.alloc(subsampled_width, subsampled_height, plane->m_bit_depth, m_chroma)) { + if (!newPlane.alloc(subsampled_width, subsampled_height, plane->m_datatype, plane->m_bit_depth, num_interleaved_pixels_per_plane(m_chroma))) { return false; } // copy the visible part of the old plane into the new plane - for (int y = 0; y < plane->m_height; y++) { - memcpy(&newPlane.mem[y * newPlane.stride], - &plane->mem[y * plane->stride], - plane->m_width); + for (uint32_t y = 0; y < plane->m_height; y++) { + memcpy(static_cast(newPlane.mem) + y * newPlane.stride, + static_cast(plane->mem) + y * plane->stride, + plane->m_width * bytes_per_pixel); } planeIter.second = newPlane; @@ -244,29 +321,103 @@ bool HeifPixelImage::extend_padding_to_size(int width, int height) // extend plane size - int bytes_per_pixel = (plane->m_bit_depth + 7) / 8; - - for (int y = 0; y < old_height; y++) { - for (int x = old_width; x < subsampled_width; x++) { - memcpy(&plane->mem[y * plane->stride + x * bytes_per_pixel], - &plane->mem[y * plane->stride + (plane->m_width - 1) * bytes_per_pixel], - bytes_per_pixel); + if (old_width != subsampled_width) { + for (uint32_t y = 0; y < old_height; y++) { + for (uint32_t x = old_width; x < subsampled_width; x++) { + memcpy(static_cast(plane->mem) + y * plane->stride + x * bytes_per_pixel, + static_cast(plane->mem) + y * plane->stride + (old_width - 1) * bytes_per_pixel, + bytes_per_pixel); + } } } - for (int y = old_height; y < subsampled_height; y++) { - memcpy(&plane->mem[y * plane->stride], - &plane->mem[(plane->m_height - 1) * plane->stride], + for (uint32_t y = old_height; y < subsampled_height; y++) { + memcpy(static_cast(plane->mem) + y * plane->stride, + static_cast(plane->mem) + (old_height - 1) * plane->stride, subsampled_width * bytes_per_pixel); } + + + if (adjust_size) { + plane->m_width = subsampled_width; + plane->m_height = subsampled_height; + } } - // don't modify the logical image size + // modify logical image size, if requested + + if (adjust_size) { + m_width = width; + m_height = height; + } return true; } +bool HeifPixelImage::extend_to_size_with_zero(uint32_t width, uint32_t height) +{ + for (auto& planeIter : m_planes) { + auto* plane = &planeIter.second; + + uint32_t subsampled_width, subsampled_height; + get_subsampled_size(width, height, planeIter.first, m_chroma, + &subsampled_width, &subsampled_height); + + uint32_t old_width = plane->m_width; + uint32_t old_height = plane->m_height; + + int bytes_per_pixel = get_storage_bits_per_pixel(planeIter.first) / 8; + + if (plane->m_mem_width < subsampled_width || + plane->m_mem_height < subsampled_height) { + + ImagePlane newPlane; + if (!newPlane.alloc(subsampled_width, subsampled_height, plane->m_datatype, plane->m_bit_depth, num_interleaved_pixels_per_plane(m_chroma))) { + return false; + } + + // copy the visible part of the old plane into the new plane + + for (uint32_t y = 0; y < plane->m_height; y++) { + memcpy(static_cast(newPlane.mem) + y * newPlane.stride, + static_cast(plane->mem) + y * plane->stride, + plane->m_width * bytes_per_pixel); + } + + planeIter.second = newPlane; + plane = &planeIter.second; + } + + // extend plane size + + if (old_width != subsampled_width) { + for (uint32_t y = 0; y < old_height; y++) { + memset(static_cast(plane->mem) + y * plane->stride + old_width * bytes_per_pixel, + 0, + bytes_per_pixel * (subsampled_width - old_width)); + } + } + + for (uint32_t y = old_height; y < subsampled_height; y++) { + memset(static_cast(plane->mem) + y * plane->stride, + 0, + subsampled_width * bytes_per_pixel); + } + + + plane->m_width = subsampled_width; + plane->m_height = subsampled_height; + } + + // modify the logical image size + + m_width = width; + m_height = height; + + return true; +} + bool HeifPixelImage::has_channel(heif_channel channel) const { return (m_planes.find(channel) != m_planes.end()); @@ -282,22 +433,22 @@ bool HeifPixelImage::has_alpha() const } -int HeifPixelImage::get_width(enum heif_channel channel) const +uint32_t HeifPixelImage::get_width(enum heif_channel channel) const { auto iter = m_planes.find(channel); if (iter == m_planes.end()) { - return -1; + return 0; } return iter->second.m_width; } -int HeifPixelImage::get_height(enum heif_channel channel) const +uint32_t HeifPixelImage::get_height(enum heif_channel channel) const { auto iter = m_planes.find(channel); if (iter == m_planes.end()) { - return -1; + return 0; } return iter->second.m_height; @@ -354,33 +505,25 @@ uint8_t HeifPixelImage::get_bits_per_pixel(enum heif_channel channel) const } -uint8_t* HeifPixelImage::get_plane(enum heif_channel channel, int* out_stride) +heif_channel_datatype HeifPixelImage::get_datatype(enum heif_channel channel) const { auto iter = m_planes.find(channel); if (iter == m_planes.end()) { - return nullptr; - } - - if (out_stride) { - *out_stride = iter->second.stride; + return heif_channel_datatype_undefined; } - return iter->second.mem; + return iter->second.m_datatype; } -const uint8_t* HeifPixelImage::get_plane(enum heif_channel channel, int* out_stride) const +int HeifPixelImage::get_number_of_interleaved_components(heif_channel channel) const { auto iter = m_planes.find(channel); if (iter == m_planes.end()) { - return nullptr; - } - - if (out_stride) { - *out_stride = iter->second.stride; + return 0; } - return iter->second.mem; + return iter->second.m_num_interleaved_components; } @@ -388,54 +531,94 @@ void HeifPixelImage::copy_new_plane_from(const std::shared_ptrget_width(src_channel); - int height = src_image->get_height(src_channel); + uint32_t width = src_image->get_width(src_channel); + uint32_t height = src_image->get_height(src_channel); assert(!has_channel(dst_channel)); - add_plane(dst_channel, width, height, src_image->get_bits_per_pixel(src_channel)); + const auto& src_plane = src_image->m_planes.find(src_channel)->second; + + add_channel(dst_channel, width, height, + src_plane.m_datatype, + src_image->get_bits_per_pixel(src_channel)); uint8_t* dst; - int dst_stride = 0; + uint32_t dst_stride = 0; const uint8_t* src; - int src_stride = 0; + uint32_t src_stride = 0; src = src_image->get_plane(src_channel, &src_stride); dst = get_plane(dst_channel, &dst_stride); - int bpl = width * (src_image->get_storage_bits_per_pixel(src_channel) / 8); + uint32_t bpl = width * (src_image->get_storage_bits_per_pixel(src_channel) / 8); - for (int y = 0; y < height; y++) { + for (uint32_t y = 0; y < height; y++) { memcpy(dst + y * dst_stride, src + y * src_stride, bpl); } } + +void HeifPixelImage::extract_alpha_from_RGBA(const std::shared_ptr& src_image) +{ + uint32_t width = src_image->get_width(); + uint32_t height = src_image->get_height(); + + add_plane(heif_channel_Y, width, height, src_image->get_bits_per_pixel(heif_channel_interleaved)); + + uint8_t* dst; + uint32_t dst_stride = 0; + + const uint8_t* src; + uint32_t src_stride = 0; + + src = src_image->get_plane(heif_channel_interleaved, &src_stride); + dst = get_plane(heif_channel_Y, &dst_stride); + + //int bpl = width * (src_image->get_storage_bits_per_pixel(src_channel) / 8); + + for (uint32_t y = 0; y < height; y++) { + for (uint32_t x = 0; x < width; x++) { + dst[y * dst_stride + x] = src[y * src_stride + 4 * x + 3]; + } + } +} + + void HeifPixelImage::fill_new_plane(heif_channel dst_channel, uint16_t value, int width, int height, int bpp) { add_plane(dst_channel, width, height, bpp); + fill_plane(dst_channel, value); +} + +void HeifPixelImage::fill_plane(heif_channel dst_channel, uint16_t value) +{ int num_interleaved = num_interleaved_pixels_per_plane(m_chroma); + int bpp = get_bits_per_pixel(dst_channel); + uint32_t width = get_width(dst_channel); + uint32_t height = get_height(dst_channel); + if (bpp <= 8) { uint8_t* dst; - int dst_stride = 0; + uint32_t dst_stride = 0; dst = get_plane(dst_channel, &dst_stride); - int width_bytes = width * num_interleaved; + uint32_t width_bytes = width * num_interleaved; - for (int y = 0; y < height; y++) { + for (uint32_t y = 0; y < height; y++) { memset(dst + y * dst_stride, value, width_bytes); } } else { uint16_t* dst; - int dst_stride = 0; + uint32_t dst_stride = 0; dst = (uint16_t*) get_plane(dst_channel, &dst_stride); dst_stride /= 2; - for (int y = 0; y < height; y++) { - for (int x = 0; x < width * num_interleaved; x++) { + for (uint32_t y = 0; y < height; y++) { + for (uint32_t x = 0; x < width * num_interleaved; x++) { dst[y * dst_stride + x] = value; } } @@ -456,7 +639,7 @@ void HeifPixelImage::transfer_plane_from_image_as(const std::shared_ptr& out_img) +Error HeifPixelImage::copy_image_to(const std::shared_ptr& source, uint32_t x0, uint32_t y0) { - // --- create output image (or simply reuse existing image) + std::set channels = source->get_channel_set(); + + uint32_t w = get_width(); + uint32_t h = get_height(); + heif_chroma chroma = get_chroma_format(); + + + for (heif_channel channel : channels) { + + uint32_t tile_stride; + const uint8_t* tile_data = source->get_plane(channel, &tile_stride); + + uint32_t out_stride; + uint8_t* out_data = get_plane(channel, &out_stride); + + if (w <= x0 || h <= y0) { + return {heif_error_Invalid_input, + heif_suberror_Invalid_grid_data}; + } + + if (source->get_bits_per_pixel(channel) != get_bits_per_pixel(channel)) { + return {heif_error_Invalid_input, + heif_suberror_Wrong_tile_image_pixel_depth}; + } + + uint32_t src_width = source->get_width(channel); + uint32_t src_height = source->get_height(channel); + + uint32_t copy_width = std::min(src_width, channel_width(w - x0, chroma, channel)); + uint32_t copy_height = std::min(src_height, channel_height(h - y0, chroma, channel)); + + copy_width *= source->get_storage_bits_per_pixel(channel) / 8; + + uint32_t xs = channel_width(x0, chroma, channel); + uint32_t ys = channel_height(y0, chroma, channel); + xs *= source->get_storage_bits_per_pixel(channel) / 8; + + for (uint32_t py = 0; py < copy_height; py++) { + memcpy(out_data + xs + (ys + py) * out_stride, + tile_data + py * tile_stride, + copy_width); + } + } + + return Error::Ok; +} + + +Result> HeifPixelImage::rotate_ccw(int angle_degrees) +{ + // --- for some subsampled chroma colorspaces, we have to transform to 4:4:4 before rotation + + bool need_conversion = false; + + if (get_chroma_format() == heif_chroma_422) { + if (angle_degrees == 90 || angle_degrees == 270) { + need_conversion = true; + } + else if (angle_degrees == 180 && has_odd_height()) { + need_conversion = true; + } + } + else if (get_chroma_format() == heif_chroma_420) { + if (angle_degrees == 90 && has_odd_width()) { + need_conversion = true; + } + else if (angle_degrees == 180 && (has_odd_width() || has_odd_height())) { + need_conversion = true; + } + else if (angle_degrees == 270 && has_odd_height()) { + need_conversion = true; + } + } + + if (need_conversion) { + heif_color_conversion_options options{}; + heif_color_conversion_options_set_defaults(&options); + + auto converted_image = convert_colorspace(shared_from_this(), heif_colorspace_YCbCr, heif_chroma_444, nullptr, get_bits_per_pixel(heif_channel_Y), options); + return converted_image->rotate_ccw(angle_degrees); + } + + + // --- create output image if (angle_degrees == 0) { - out_img = shared_from_this(); - return Error::Ok; + return shared_from_this(); } - int out_width = m_width; - int out_height = m_height; + uint32_t out_width = m_width; + uint32_t out_height = m_height; if (angle_degrees == 90 || angle_degrees == 270) { std::swap(out_width, out_height); } - out_img = std::make_shared(); + std::shared_ptr out_img = std::make_shared(); out_img->create(out_width, out_height, m_colorspace, m_chroma); // --- rotate all channels - for (const auto& plane_pair : m_planes) { + for (const auto &plane_pair: m_planes) { heif_channel channel = plane_pair.first; - const ImagePlane& plane = plane_pair.second; + const ImagePlane &plane = plane_pair.second; /* if (plane.bit_depth != 8) { @@ -515,119 +779,198 @@ Error HeifPixelImage::rotate_ccw(int angle_degrees, } */ - int out_plane_width = plane.m_width; - int out_plane_height = plane.m_height; + uint32_t out_plane_width = plane.m_width; + uint32_t out_plane_height = plane.m_height; if (angle_degrees == 90 || angle_degrees == 270) { std::swap(out_plane_width, out_plane_height); } - out_img->add_plane(channel, out_plane_width, out_plane_height, plane.m_bit_depth); + out_img->add_channel(channel, out_plane_width, out_plane_height, plane.m_datatype, plane.m_bit_depth); + ImagePlane& out_plane = out_img->m_planes.find(channel)->second; - int w = plane.m_width; - int h = plane.m_height; + if (plane.m_bit_depth <= 8) { + plane.rotate_ccw(angle_degrees, out_plane); + } + else if (plane.m_bit_depth <= 16) { + plane.rotate_ccw(angle_degrees, out_plane); + } + else if (plane.m_bit_depth <= 32) { + plane.rotate_ccw(angle_degrees, out_plane); + } + else if (plane.m_bit_depth <= 64) { + plane.rotate_ccw(angle_degrees, out_plane); + } + else if (plane.m_bit_depth <= 128) { + plane.rotate_ccw(angle_degrees, out_plane); + } + } + // --- pass the color profiles to the new image - int in_stride = plane.stride; - const uint8_t* in_data = plane.mem; + out_img->set_color_profile_nclx(get_color_profile_nclx()); + out_img->set_color_profile_icc(get_color_profile_icc()); - int out_stride = 0; - uint8_t* out_data = out_img->get_plane(channel, &out_stride); + return out_img; +} - if (plane.m_bit_depth == 8) { - if (angle_degrees == 270) { - for (int x = 0; x < h; x++) - for (int y = 0; y < w; y++) { - out_data[y * out_stride + x] = in_data[(h - 1 - x) * in_stride + y]; - } - } - else if (angle_degrees == 180) { - for (int y = 0; y < h; y++) - for (int x = 0; x < w; x++) { - out_data[y * out_stride + x] = in_data[(h - 1 - y) * in_stride + (w - 1 - x)]; - } - } - else if (angle_degrees == 90) { - for (int x = 0; x < h; x++) - for (int y = 0; y < w; y++) { - out_data[y * out_stride + x] = in_data[x * in_stride + (w - 1 - y)]; - } - } - } - else { // 16 bit (TODO: unchecked code) - if (angle_degrees == 270) { - for (int x = 0; x < h; x++) - for (int y = 0; y < w; y++) { - out_data[y * out_stride + 2 * x] = in_data[(h - 1 - x) * in_stride + 2 * y]; - out_data[y * out_stride + 2 * x + 1] = in_data[(h - 1 - x) * in_stride + 2 * y + 1]; - } +template +void HeifPixelImage::ImagePlane::rotate_ccw(int angle_degrees, + ImagePlane& out_plane) const +{ + uint32_t w = m_width; + uint32_t h = m_height; + + uint32_t in_stride = stride / uint32_t(sizeof(T)); + const T* in_data = static_cast(mem); + + uint32_t out_stride = out_plane.stride / uint32_t(sizeof(T)); + T* out_data = static_cast(out_plane.mem); + + if (angle_degrees == 270) { + for (uint32_t x = 0; x < h; x++) + for (uint32_t y = 0; y < w; y++) { + out_data[y * out_stride + x] = in_data[(h - 1 - x) * in_stride + y]; } - else if (angle_degrees == 180) { - for (int y = 0; y < h; y++) - for (int x = 0; x < w; x++) { - out_data[y * out_stride + 2 * x] = in_data[(h - 1 - y) * in_stride + 2 * (w - 1 - x)]; - out_data[y * out_stride + 2 * x + 1] = in_data[(h - 1 - y) * in_stride + 2 * (w - 1 - x) + 1]; - } + } else if (angle_degrees == 180) { + for (uint32_t y = 0; y < h; y++) + for (uint32_t x = 0; x < w; x++) { + out_data[y * out_stride + x] = in_data[(h - 1 - y) * in_stride + (w - 1 - x)]; } - else if (angle_degrees == 90) { - for (int x = 0; x < h; x++) - for (int y = 0; y < w; y++) { - out_data[y * out_stride + 2 * x] = in_data[x * in_stride + 2 * (w - 1 - y)]; - out_data[y * out_stride + 2 * x + 1] = in_data[x * in_stride + 2 * (w - 1 - y) + 1]; - } + } else if (angle_degrees == 90) { + for (uint32_t x = 0; x < h; x++) + for (uint32_t y = 0; y < w; y++) { + out_data[y * out_stride + x] = in_data[x * in_stride + (w - 1 - y)]; } - } } +} - // --- pass the color profiles to the new image - out_img->set_color_profile_nclx(get_color_profile_nclx()); - out_img->set_color_profile_icc(get_color_profile_icc()); +template +void HeifPixelImage::ImagePlane::mirror_inplace(heif_transform_mirror_direction direction) +{ + uint32_t w = m_width; + uint32_t h = m_height; - return Error::Ok; + T* data = static_cast(mem); + + if (direction == heif_transform_mirror_direction_horizontal) { + for (uint32_t y = 0; y < h; y++) { + for (uint32_t x = 0; x < w / 2; x++) + std::swap(data[y * stride / sizeof(T) + x], data[y * stride / sizeof(T) + w - 1 - x]); + } + } else { + for (uint32_t y = 0; y < h / 2; y++) { + for (uint32_t x = 0; x < w; x++) + std::swap(data[y * stride / sizeof(T) + x], data[(h - 1 - y) * stride / sizeof(T) + x]); + } + } } -Error HeifPixelImage::mirror_inplace(heif_transform_mirror_direction direction) +Result> HeifPixelImage::mirror_inplace(heif_transform_mirror_direction direction) { - for (auto& plane_pair : m_planes) { - ImagePlane& plane = plane_pair.second; + // --- for some subsampled chroma colorspaces, we have to transform to 4:4:4 before rotation - if (plane.m_bit_depth != 8) { - return Error(heif_error_Unsupported_feature, - heif_suberror_Unspecified, - "Can currently only mirror images with 8 bits per pixel"); + bool need_conversion = false; + + if (get_chroma_format() == heif_chroma_422) { + if (direction == heif_transform_mirror_direction_horizontal && has_odd_width()) { + need_conversion = true; } + } + else if (get_chroma_format() == heif_chroma_420) { + if (has_odd_width() || has_odd_height()) { + need_conversion = true; + } + } + if (need_conversion) { + heif_color_conversion_options options{}; + heif_color_conversion_options_set_defaults(&options); - int w = plane.m_width; - int h = plane.m_height; + auto converted_image = convert_colorspace(shared_from_this(), heif_colorspace_YCbCr, heif_chroma_444, nullptr, get_bits_per_pixel(heif_channel_Y), options); + return converted_image->mirror_inplace(direction); + } - int stride = plane.stride; - uint8_t* data = plane.mem; - if (direction == heif_transform_mirror_direction_horizontal) { - for (int y = 0; y < h; y++) { - for (int x = 0; x < w / 2; x++) - std::swap(data[y * stride + x], data[y * stride + w - 1 - x]); - } + for (auto& plane_pair : m_planes) { + ImagePlane& plane = plane_pair.second; + + if (plane.m_bit_depth <= 8) { + plane.mirror_inplace(direction); + } + else if (plane.m_bit_depth <= 16) { + plane.mirror_inplace(direction); + } + else if (plane.m_bit_depth <= 32) { + plane.mirror_inplace(direction); + } + else if (plane.m_bit_depth <= 64) { + plane.mirror_inplace(direction); + } + else if (plane.m_bit_depth <= 128) { + plane.mirror_inplace(direction); } else { - for (int y = 0; y < h / 2; y++) { - for (int x = 0; x < w; x++) - std::swap(data[y * stride + x], data[(h - 1 - y) * stride + x]); - } + std::stringstream sstr; + sstr << "Cannot mirror images with " << plane.m_bit_depth << " bits per pixel"; + return Error{heif_error_Unsupported_feature, + heif_suberror_Unspecified, + sstr.str()}; } } - return Error::Ok; + return shared_from_this(); } -Error HeifPixelImage::crop(int left, int right, int top, int bottom, - std::shared_ptr& out_img) const +int HeifPixelImage::ImagePlane::get_bytes_per_pixel() const { - out_img = std::make_shared(); + if (m_bit_depth <= 8) { + return 1; + } + else if (m_bit_depth <= 16) { + return 2; + } + else if (m_bit_depth <= 32) { + return 4; + } + else if (m_bit_depth <= 64) { + return 8; + } + else { + assert(m_bit_depth <= 128); + return 16; + } +} + + +Result> HeifPixelImage::crop(uint32_t left, uint32_t right, uint32_t top, uint32_t bottom) const +{ + // --- for some subsampled chroma colorspaces, we have to transform to 4:4:4 before rotation + + bool need_conversion = false; + + if (get_chroma_format() == heif_chroma_422 && (left & 1) == 1) { + need_conversion = true; + } + else if (get_chroma_format() == heif_chroma_420 && + ((left & 1) == 1 || (top & 1) == 1)) { + need_conversion = true; + } + + if (need_conversion) { + heif_color_conversion_options options{}; + heif_color_conversion_options_set_defaults(&options); + + auto converted_image = convert_colorspace(shared_from_this(), heif_colorspace_YCbCr, heif_chroma_444, nullptr, get_bits_per_pixel(heif_channel_Y), options); + return converted_image->crop(left, right, top, bottom); + } + + + + auto out_img = std::make_shared(); out_img->create(right - left + 1, bottom - top + 1, m_colorspace, m_chroma); @@ -637,46 +980,24 @@ Error HeifPixelImage::crop(int left, int right, int top, int bottom, heif_channel channel = plane_pair.first; const ImagePlane& plane = plane_pair.second; - if (false && plane.m_bit_depth != 8) { - return Error(heif_error_Unsupported_feature, - heif_suberror_Unspecified, - "Can currently only crop images with 8 bits per pixel"); - } + uint32_t w = plane.m_width; + uint32_t h = plane.m_height; + uint32_t plane_left = left * w / m_width; + uint32_t plane_right = right * w / m_width; + uint32_t plane_top = top * h / m_height; + uint32_t plane_bottom = bottom * h / m_height; - int w = plane.m_width; - int h = plane.m_height; + out_img->add_channel(channel, + plane_right - plane_left + 1, + plane_bottom - plane_top + 1, + plane.m_datatype, + plane.m_bit_depth); - int plane_left = left * w / m_width; - int plane_right = right * w / m_width; - int plane_top = top * h / m_height; - int plane_bottom = bottom * h / m_height; + ImagePlane& out_plane = out_img->m_planes.find(channel)->second; - out_img->add_plane(channel, - plane_right - plane_left + 1, - plane_bottom - plane_top + 1, - plane.m_bit_depth); - - int in_stride = plane.stride; - const uint8_t* in_data = plane.mem; - - int out_stride = 0; - uint8_t* out_data = out_img->get_plane(channel, &out_stride); - - if (plane.m_bit_depth == 8) { - for (int y = plane_top; y <= plane_bottom; y++) { - memcpy(&out_data[(y - plane_top) * out_stride], - &in_data[y * in_stride + plane_left], - plane_right - plane_left + 1); - } - } - else { - for (int y = plane_top; y <= plane_bottom; y++) { - memcpy(&out_data[(y - plane_top) * out_stride], - &in_data[y * in_stride + plane_left * 2], - (plane_right - plane_left + 1) * 2); - } - } + int bytes_per_pixel = plane.get_bytes_per_pixel(); + plane.crop(plane_left, plane_right, plane_top, plane_bottom, bytes_per_pixel, out_plane); } // --- pass the color profiles to the new image @@ -684,7 +1005,24 @@ Error HeifPixelImage::crop(int left, int right, int top, int bottom, out_img->set_color_profile_nclx(get_color_profile_nclx()); out_img->set_color_profile_icc(get_color_profile_icc()); - return Error::Ok; + return out_img; +} + + +void HeifPixelImage::ImagePlane::crop(uint32_t left, uint32_t right, uint32_t top, uint32_t bottom, + int bytes_per_pixel, ImagePlane& out_plane) const +{ + uint32_t in_stride = stride; + auto* in_data = static_cast(mem); + + uint32_t out_stride = out_plane.stride; + auto* out_data = static_cast(out_plane.mem); + + for (uint32_t y = top; y <= bottom; y++) { + memcpy(&out_data[(y - top) * out_stride], + &in_data[y * in_stride + left * bytes_per_pixel], + (right - left + 1) * bytes_per_pixel); + } } @@ -700,23 +1038,23 @@ Error HeifPixelImage::fill_RGB_16bit(uint16_t r, uint16_t g, uint16_t b, uint16_ continue; } - return Error(heif_error_Usage_error, - heif_suberror_Nonexisting_image_channel_referenced); + return {heif_error_Usage_error, + heif_suberror_Nonexisting_image_channel_referenced}; } ImagePlane& plane = plane_iter->second; if (plane.m_bit_depth != 8) { - return Error(heif_error_Unsupported_feature, - heif_suberror_Unspecified, - "Can currently only fill images with 8 bits per pixel"); + return {heif_error_Unsupported_feature, + heif_suberror_Unspecified, + "Can currently only fill images with 8 bits per pixel"}; } - int h = plane.m_height; + size_t h = plane.m_height; - int stride = plane.stride; - uint8_t* data = plane.mem; + size_t stride = plane.stride; + auto* data = static_cast(plane.mem); uint16_t val16; switch (channel) { @@ -741,21 +1079,49 @@ Error HeifPixelImage::fill_RGB_16bit(uint16_t r, uint16_t g, uint16_t b, uint16_ auto val8 = static_cast(val16 >> 8U); - memset(data, val8, stride * h); + + // memset() even when h * stride > sizeof(size_t) + + if (std::numeric_limits::max() / stride > h) { + // can fill in one step + memset(data, val8, stride * h); + } + else { + // fill line by line + auto* p = data; + + for (size_t y=0;y& overlay, int dx, int dy) +uint32_t negate_negative_int32(int32_t x) +{ + assert(x <= 0); + + if (x == INT32_MIN) { + return static_cast(INT32_MAX) + 1; + } + else { + return static_cast(-x); + } +} + + +Error HeifPixelImage::overlay(std::shared_ptr& overlay, int32_t dx, int32_t dy) { std::set channels = overlay->get_channel_set(); bool has_alpha = overlay->has_channel(heif_channel_Alpha); //bool has_alpha_me = has_channel(heif_channel_Alpha); - int alpha_stride = 0; + uint32_t alpha_stride = 0; uint8_t* alpha_p; alpha_p = overlay->get_plane(heif_channel_Alpha, &alpha_stride); @@ -764,79 +1130,97 @@ Error HeifPixelImage::overlay(std::shared_ptr& overlay, int dx, continue; } - int in_stride = 0; + uint32_t in_stride = 0; const uint8_t* in_p; - int out_stride = 0; + uint32_t out_stride = 0; uint8_t* out_p; in_p = overlay->get_plane(channel, &in_stride); out_p = get_plane(channel, &out_stride); - int in_w = overlay->get_width(channel); - int in_h = overlay->get_height(channel); - assert(in_w >= 0); - assert(in_h >= 0); + uint32_t in_w = overlay->get_width(channel); + uint32_t in_h = overlay->get_height(channel); - int out_w = get_width(channel); - int out_h = get_height(channel); - assert(out_w >= 0); - assert(out_h >= 0); + uint32_t out_w = get_width(channel); + uint32_t out_h = get_height(channel); - // overlay image extends past the right border -> cut width for copy - if (dx + in_w > out_w) { - in_w = out_w - dx; - } + // top-left points where to start copying in source and destination + uint32_t in_x0; + uint32_t in_y0; + uint32_t out_x0; + uint32_t out_y0; - // overlay image extends past the bottom border -> cut height for copy - if (dy + in_h > out_h) { - in_h = out_h - dy; + if (dx > 0 && static_cast(dx) >= out_w) { + // the overlay image is completely outside the right border -> skip overlaying + return Error::Ok; + } + else if (dx < 0 && in_w <= negate_negative_int32(dx)) { + // the overlay image is completely outside the left border -> skip overlaying + return Error::Ok; } - // overlay image completely outside right or bottom border -> do not copy - if (in_w < 0 || in_h < 0) { - return Error(heif_error_Invalid_input, - heif_suberror_Overlay_image_outside_of_canvas, - "Overlay image outside of right or bottom canvas border"); + if (dx < 0) { + // overlay image started partially outside of left border + + in_x0 = negate_negative_int32(dx); + out_x0 = 0; + in_w = in_w - in_x0; // in_x0 < in_w because in_w > -dx = in_x0 + } + else { + in_x0 = 0; + out_x0 = static_cast(dx); } + // we know that dx >= 0 && dx < out_w - // calculate top-left point where to start copying in source and destination - int in_x0 = 0; - int in_y0 = 0; - int out_x0 = dx; - int out_y0 = dy; + if (static_cast(dx) > UINT32_MAX - in_w || + dx + in_w > out_w) { + // overlay image extends partially outside of right border - // overlay image started outside of left border - // -> move start into the image and start at left output column - if (dx < 0) { - in_x0 = -dx; - out_x0 = 0; + in_w = out_w - static_cast(dx); // we know that dx < out_w from first condition + } + + + if (dy > 0 && static_cast(dy) >= out_h) { + // the overlay image is completely outside the bottom border -> skip overlaying + return Error::Ok; + } + else if (dy < 0 && in_h <= negate_negative_int32(dy)) { + // the overlay image is completely outside the top border -> skip overlaying + return Error::Ok; } - // overlay image started outside of top border - // -> move start into the image and start at top output row if (dy < 0) { - in_y0 = -dy; + // overlay image started partially outside of top border + + in_y0 = negate_negative_int32(dy); out_y0 = 0; + in_h = in_h - in_y0; // in_y0 < in_h because in_h > -dy = in_y0 } + else { + in_y0 = 0; + out_y0 = static_cast(dy); + } + + // we know that dy >= 0 && dy < out_h - // if overlay image is completely outside at left border, do not copy anything. - if (in_w <= in_x0 || - in_h <= in_y0) { - return Error(heif_error_Invalid_input, - heif_suberror_Overlay_image_outside_of_canvas, - "Overlay image outside of left or top canvas border"); + if (static_cast(dy) > UINT32_MAX - in_h || + dy + in_h > out_h) { + // overlay image extends partially outside of bottom border + + in_h = out_h - static_cast(dy); // we know that dy < out_h from first condition } - for (int y = in_y0; y < in_h; y++) { + + for (uint32_t y = in_y0; y < in_h; y++) { if (!has_alpha) { memcpy(out_p + out_x0 + (out_y0 + y - in_y0) * out_stride, in_p + in_x0 + y * in_stride, in_w - in_x0); } else { - for (int x = in_x0; x < in_w; x++) { + for (uint32_t x = in_x0; x < in_w; x++) { uint8_t* outptr = &out_p[out_x0 + (out_y0 + y - in_y0) * out_stride + x]; uint8_t in_val = in_p[in_x0 + y * in_stride + x]; uint8_t alpha_val = alpha_p[in_x0 + y * in_stride + x]; @@ -852,7 +1236,7 @@ Error HeifPixelImage::overlay(std::shared_ptr& overlay, int dx, Error HeifPixelImage::scale_nearest_neighbor(std::shared_ptr& out_img, - int width, int height) const + uint32_t width, uint32_t height) const { out_img = std::make_shared(); out_img->create(width, height, m_colorspace, m_chroma); @@ -868,7 +1252,7 @@ Error HeifPixelImage::scale_nearest_neighbor(std::shared_ptr& ou if (!has_channel(heif_channel_R) || !has_channel(heif_channel_G) || !has_channel(heif_channel_B)) { - return Error(heif_error_Invalid_input, heif_suberror_Unspecified, "RGB input without R,G,B, planes"); + return {heif_error_Invalid_input, heif_suberror_Unspecified, "RGB input without R,G,B, planes"}; } out_img->add_plane(heif_channel_R, width, height, get_bits_per_pixel(heif_channel_R)); @@ -877,7 +1261,7 @@ Error HeifPixelImage::scale_nearest_neighbor(std::shared_ptr& ou } else if (get_colorspace() == heif_colorspace_monochrome) { if (!has_channel(heif_channel_Y)) { - return Error(heif_error_Invalid_input, heif_suberror_Unspecified, "monochrome input with no Y plane"); + return {heif_error_Invalid_input, heif_suberror_Unspecified, "monochrome input with no Y plane"}; } out_img->add_plane(heif_channel_Y, width, height, get_bits_per_pixel(heif_channel_Y)); @@ -886,17 +1270,17 @@ Error HeifPixelImage::scale_nearest_neighbor(std::shared_ptr& ou if (!has_channel(heif_channel_Y) || !has_channel(heif_channel_Cb) || !has_channel(heif_channel_Cr)) { - return Error(heif_error_Invalid_input, heif_suberror_Unspecified, "YCbCr image without Y,Cb,Cr planes"); + return {heif_error_Invalid_input, heif_suberror_Unspecified, "YCbCr image without Y,Cb,Cr planes"}; } - int cw, ch; + uint32_t cw, ch; get_subsampled_size(width, height, heif_channel_Cb, get_chroma_format(), &cw, &ch); out_img->add_plane(heif_channel_Y, width, height, get_bits_per_pixel(heif_channel_Y)); out_img->add_plane(heif_channel_Cb, cw, ch, get_bits_per_pixel(heif_channel_Cb)); out_img->add_plane(heif_channel_Cr, cw, ch, get_bits_per_pixel(heif_channel_Cr)); } else { - return Error(heif_error_Invalid_input, heif_suberror_Unspecified, "unknown color configuration"); + return {heif_error_Invalid_input, heif_suberror_Unspecified, "unknown color configuration"}; } if (has_channel(heif_channel_Alpha)) { @@ -914,32 +1298,39 @@ Error HeifPixelImage::scale_nearest_neighbor(std::shared_ptr& ou const int bpp = get_storage_bits_per_pixel(channel) / 8; if (!out_img->has_channel(channel)) { - return Error(heif_error_Invalid_input, heif_suberror_Unspecified, "scaling input has extra color plane"); + return {heif_error_Invalid_input, heif_suberror_Unspecified, "scaling input has extra color plane"}; + } + + + if (plane.m_bit_depth != 8) { + return {heif_error_Unsupported_feature, + heif_suberror_Unspecified, + "Can currently only crop images with 8 bits per pixel"}; } - int out_w = out_img->get_width(channel); - int out_h = out_img->get_height(channel); + uint32_t out_w = out_img->get_width(channel); + uint32_t out_h = out_img->get_height(channel); - int in_stride = plane.stride; - const uint8_t* in_data = plane.mem; + uint32_t in_stride = plane.stride; + const auto* in_data = static_cast(plane.mem); - int out_stride = 0; - uint8_t* out_data = out_img->get_plane(channel, &out_stride); + uint32_t out_stride = 0; + auto* out_data = static_cast(out_img->get_plane(channel, &out_stride)); - for (int y = 0; y < out_h; y++) { - int iy = y * m_height / height; + for (uint32_t y = 0; y < out_h; y++) { + uint32_t iy = y * m_height / height; if (bpp == 1) { - for (int x = 0; x < out_w; x++) { - int ix = x * m_width / width; + for (uint32_t x = 0; x < out_w; x++) { + uint32_t ix = x * m_width / width; out_data[y * out_stride + x] = in_data[iy * in_stride + ix]; } } else { - for (int x = 0; x < out_w; x++) { - int ix = x * m_width / width; + for (uint32_t x = 0; x < out_w; x++) { + uint32_t ix = x * m_width / width; for (int b = 0; b < bpp; b++) { out_data[y * out_stride + bpp * x + b] = in_data[iy * in_stride + bpp * ix + b]; @@ -957,7 +1348,7 @@ void HeifPixelImage::debug_dump() const { auto channels = get_channel_set(); for (auto c : channels) { - int stride = 0; + uint32_t stride = 0; const uint8_t* p = get_plane(c, &stride); for (int y = 0; y < 8; y++) { @@ -968,3 +1359,34 @@ void HeifPixelImage::debug_dump() const } } } + +void HeifPixelImage::create_clone_image_at_new_size(const std::shared_ptr& source, uint32_t w, uint32_t h) +{ + heif_colorspace colorspace = source->get_colorspace(); + heif_chroma chroma = source->get_chroma_format(); + + create(w, h, colorspace, chroma); + + switch (colorspace) { + case heif_colorspace_monochrome: + add_plane(heif_channel_Y, w, h, source->get_bits_per_pixel(heif_channel_Y)); + break; + case heif_colorspace_YCbCr: + add_plane(heif_channel_Y, w, h, source->get_bits_per_pixel(heif_channel_Y)); + add_plane(heif_channel_Cb, chroma_width(w, chroma), chroma_height(h, chroma), source->get_bits_per_pixel(heif_channel_Cb)); + add_plane(heif_channel_Cr, chroma_width(w, chroma), chroma_height(h, chroma), source->get_bits_per_pixel(heif_channel_Cr)); + break; + case heif_colorspace_RGB: + add_plane(heif_channel_R, w, h, source->get_bits_per_pixel(heif_channel_R)); + add_plane(heif_channel_G, w, h, source->get_bits_per_pixel(heif_channel_G)); + add_plane(heif_channel_B, w, h, source->get_bits_per_pixel(heif_channel_B)); + break; + default: + assert(false); + break; + } + + if (source->has_alpha()) { + add_plane(heif_channel_Alpha, w, h, source->get_bits_per_pixel(heif_channel_Alpha)); + } +} diff --git a/libheif/pixelimage.h b/libheif/pixelimage.h index d050776cb8..0b24b852f9 100644 --- a/libheif/pixelimage.h +++ b/libheif/pixelimage.h @@ -25,27 +25,52 @@ //#include "heif.h" #include "error.h" #include "nclx.h" +#include #include #include #include #include #include +#include heif_chroma chroma_from_subsampling(int h, int v); -bool is_chroma_with_alpha(heif_chroma chroma); +uint32_t chroma_width(uint32_t w, heif_chroma chroma); + +uint32_t chroma_height(uint32_t h, heif_chroma chroma); + +uint32_t channel_width(uint32_t w, heif_chroma chroma, heif_channel channel); + +uint32_t channel_height(uint32_t h, heif_chroma chroma, heif_channel channel); + +bool is_interleaved_with_alpha(heif_chroma chroma); int num_interleaved_pixels_per_plane(heif_chroma chroma); -bool is_integer_multiple_of_chroma_size(int width, - int height, +bool is_integer_multiple_of_chroma_size(uint32_t width, + uint32_t height, heif_chroma chroma); // Returns the list of valid heif_chroma values for a given colorspace. std::vector get_valid_chroma_values_for_colorspace(heif_colorspace colorspace); +// TODO: move to public API when used +enum heif_chroma420_sample_position { + // values 0-5 according to ISO 23091-2 / ITU-T H.273 + heif_chroma420_sample_position_00_05 = 0, + heif_chroma420_sample_position_05_05 = 1, + heif_chroma420_sample_position_00_00 = 2, + heif_chroma420_sample_position_05_00 = 3, + heif_chroma420_sample_position_00_10 = 4, + heif_chroma420_sample_position_05_10 = 5, + + // values 6 according to ISO 23001-17 + heif_chroma420_sample_position_00_00_01_00 = 6 +}; + + class HeifPixelImage : public std::enable_shared_from_this, public ErrorBuffer { @@ -54,9 +79,13 @@ class HeifPixelImage : public std::enable_shared_from_this, ~HeifPixelImage(); - void create(int width, int height, heif_colorspace colorspace, heif_chroma chroma); + void create(uint32_t width, uint32_t height, heif_colorspace colorspace, heif_chroma chroma); + + void create_clone_image_at_new_size(const std::shared_ptr& source, uint32_t w, uint32_t h); - bool add_plane(heif_channel channel, int width, int height, int bit_depth); + bool add_plane(heif_channel channel, uint32_t width, uint32_t height, int bit_depth); + + bool add_channel(heif_channel channel, uint32_t width, uint32_t height, heif_channel_datatype datatype, int bit_depth); bool has_channel(heif_channel channel) const; @@ -67,13 +96,17 @@ class HeifPixelImage : public std::enable_shared_from_this, void set_premultiplied_alpha(bool flag) { m_premultiplied_alpha = flag; } - int get_width() const { return m_width; } + uint32_t get_width() const { return m_width; } + + uint32_t get_height() const { return m_height; } - int get_height() const { return m_height; } + uint32_t get_width(enum heif_channel channel) const; - int get_width(enum heif_channel channel) const; + uint32_t get_height(enum heif_channel channel) const; - int get_height(enum heif_channel channel) const; + bool has_odd_width() const { return !!(m_width & 1); } + + bool has_odd_height() const { return !!(m_height & 1); } heif_chroma get_chroma_format() const { return m_chroma; } @@ -85,33 +118,67 @@ class HeifPixelImage : public std::enable_shared_from_this, uint8_t get_bits_per_pixel(enum heif_channel channel) const; - uint8_t* get_plane(enum heif_channel channel, int* out_stride); + heif_channel_datatype get_datatype(enum heif_channel channel) const; + + int get_number_of_interleaved_components(heif_channel channel) const; + + uint8_t* get_plane(enum heif_channel channel, uint32_t* out_stride) { return get_channel(channel, out_stride); } + + const uint8_t* get_plane(enum heif_channel channel, uint32_t* out_stride) const { return get_channel(channel, out_stride); } + + template + T* get_channel(enum heif_channel channel, uint32_t* out_stride) + { + auto iter = m_planes.find(channel); + if (iter == m_planes.end()) { + if (out_stride) + *out_stride = 0; + + return nullptr; + } + + if (out_stride) { + *out_stride = static_cast(iter->second.stride / sizeof(T)); + } + + //assert(sizeof(T) == iter->second.get_bytes_per_pixel()); + + return static_cast(iter->second.mem); + } - const uint8_t* get_plane(enum heif_channel channel, int* out_stride) const; + template + const T* get_channel(enum heif_channel channel, uint32_t* out_stride) const + { + return const_cast(this)->get_channel(channel, out_stride); + } void copy_new_plane_from(const std::shared_ptr& src_image, heif_channel src_channel, heif_channel dst_channel); + void extract_alpha_from_RGBA(const std::shared_ptr& srcimage); + + void fill_plane(heif_channel dst_channel, uint16_t value); + void fill_new_plane(heif_channel dst_channel, uint16_t value, int width, int height, int bpp); void transfer_plane_from_image_as(const std::shared_ptr& source, heif_channel src_channel, heif_channel dst_channel); - Error rotate_ccw(int angle_degrees, - std::shared_ptr& out_img); + Error copy_image_to(const std::shared_ptr& source, uint32_t x0, uint32_t y0); + + Result> rotate_ccw(int angle_degrees); - Error mirror_inplace(heif_transform_mirror_direction); + Result> mirror_inplace(heif_transform_mirror_direction); - Error crop(int left, int right, int top, int bottom, - std::shared_ptr& out_img) const; + Result> crop(uint32_t left, uint32_t right, uint32_t top, uint32_t bottom) const; Error fill_RGB_16bit(uint16_t r, uint16_t g, uint16_t b, uint16_t a); - Error overlay(std::shared_ptr& overlay, int dx, int dy); + Error overlay(std::shared_ptr& overlay, int32_t dx, int32_t dy); - Error scale_nearest_neighbor(std::shared_ptr& output, int width, int height) const; + Error scale_nearest_neighbor(std::shared_ptr& output, uint32_t width, uint32_t height) const; void set_color_profile_nclx(const std::shared_ptr& profile) { m_color_profile_nclx = profile; } @@ -123,7 +190,9 @@ class HeifPixelImage : public std::enable_shared_from_this, void debug_dump() const; - bool extend_padding_to_size(int width, int height); + bool extend_padding_to_size(uint32_t width, uint32_t height, bool adjust_size = false); + + bool extend_to_size_with_zero(uint32_t width, uint32_t height); // --- pixel aspect ratio @@ -167,30 +236,43 @@ class HeifPixelImage : public std::enable_shared_from_this, void add_warning(Error warning) { m_warnings.emplace_back(std::move(warning)); } + void add_warnings(const std::vector& warning) { for (const auto& err : warning) m_warnings.emplace_back(err); } + const std::vector& get_warnings() const { return m_warnings; } private: struct ImagePlane { - bool alloc(int width, int height, int bit_depth, heif_chroma chroma); + bool alloc(uint32_t width, uint32_t height, heif_channel_datatype datatype, int bit_depth, int num_interleaved_components); + heif_channel_datatype m_datatype = heif_channel_datatype_unsigned_integer; uint8_t m_bit_depth = 0; + uint8_t m_num_interleaved_components = 1; // the "visible" area of the plane - int m_width = 0; - int m_height = 0; + uint32_t m_width = 0; + uint32_t m_height = 0; // the allocated memory size - int m_mem_width = 0; - int m_mem_height = 0; + uint32_t m_mem_width = 0; + uint32_t m_mem_height = 0; - uint8_t* mem = nullptr; // aligned memory start + void* mem = nullptr; // aligned memory start uint8_t* allocated_mem = nullptr; // unaligned memory we allocated uint32_t stride = 0; // bytes per line + + int get_bytes_per_pixel() const; + + template void mirror_inplace(heif_transform_mirror_direction); + + template + void rotate_ccw(int angle_degrees, ImagePlane& out_plane) const; + + void crop(uint32_t left, uint32_t right, uint32_t top, uint32_t bottom, int bytes_per_pixel, ImagePlane& out_plane) const; }; - int m_width = 0; - int m_height = 0; + uint32_t m_width = 0; + uint32_t m_height = 0; heif_colorspace m_colorspace = heif_colorspace_undefined; heif_chroma m_chroma = heif_chroma_undefined; bool m_premultiplied_alpha = false; diff --git a/libheif/plugin_registry.cc b/libheif/plugin_registry.cc index 051d058bfb..9bc196f467 100644 --- a/libheif/plugin_registry.cc +++ b/libheif/plugin_registry.cc @@ -46,6 +46,10 @@ #include "plugins/decoder_vvdec.h" #endif +#if HAVE_VVENC +#include "plugins/encoder_vvenc.h" +#endif + #if HAVE_AOM_ENCODER #include "plugins/encoder_aom.h" #endif @@ -82,6 +86,10 @@ #include "plugins/encoder_jpeg.h" #endif +#if HAVE_OpenH264_DECODER +#include "plugins/decoder_openh264.h" +#endif + #if HAVE_OPENJPEG_ENCODER #include "plugins/encoder_openjpeg.h" #endif @@ -147,6 +155,10 @@ void register_default_plugins() register_encoder(get_encoder_plugin_uvg266()); #endif +#if HAVE_VVENC + register_encoder(get_encoder_plugin_vvenc()); +#endif + #if HAVE_VVDEC register_decoder(get_decoder_plugin_vvdec()); #endif @@ -195,6 +207,10 @@ void register_default_plugins() register_encoder(get_encoder_plugin_openjph()); #endif +#if HAVE_OpenH264_DECODER + register_decoder(get_decoder_plugin_openh264()); +#endif + #if WITH_UNCOMPRESSED_CODEC register_encoder(get_encoder_plugin_uncompressed()); #endif diff --git a/libheif/plugins/CMakeLists.txt b/libheif/plugins/CMakeLists.txt index e1a1235e57..18e4bdb158 100644 --- a/libheif/plugins/CMakeLists.txt +++ b/libheif/plugins/CMakeLists.txt @@ -22,7 +22,7 @@ macro(plugin_compilation name varName foundName optionName defineName) target_link_libraries(heif-${name} PRIVATE ${${varName}_LIBRARIES} heif) install(TARGETS heif-${name} - LIBRARY DESTINATION ${PLUGIN_DIRECTORY} + LIBRARY DESTINATION ${COMPUTED_PLUGIN_INSTALL_DIRECTORY} ) else () message("Compiling '" ${name} "' as built-in backend") @@ -104,13 +104,22 @@ set(VVDEC_sources decoder_vvdec.cc decoder_vvdec.h) set(VVDEC_extra_plugin_sources) plugin_compilation(vvdec vvdec vvdec_FOUND VVDEC VVDEC) +set(VVENC_sources encoder_vvenc.cc encoder_vvenc.h) +set(VVENC_extra_plugin_sources) +plugin_compilation(vvenc vvenc vvenc_FOUND VVENC VVENC) + +set(OpenH264_DECODER_sources decoder_openh264.cc decoder_openh264.h) +set(OpenH264_DECODER_extra_plugin_sources) +plugin_compilation(openh264dec OpenH264 OpenH264_DECODER_FOUND OpenH264_DECODER OpenH264_DECODER) + target_sources(heif PRIVATE encoder_mask.h - encoder_mask.cc) + encoder_mask.cc + nalu_utils.h + nalu_utils.cc) if (WITH_UNCOMPRESSED_CODEC) target_sources(heif PRIVATE encoder_uncompressed.h encoder_uncompressed.cc) endif () - diff --git a/libheif/plugins/decoder_dav1d.cc b/libheif/plugins/decoder_dav1d.cc index de68644125..ca17da2c3c 100644 --- a/libheif/plugins/decoder_dav1d.cc +++ b/libheif/plugins/decoder_dav1d.cc @@ -27,6 +27,8 @@ #include #include #include +#include +#include #include #include @@ -90,7 +92,13 @@ struct heif_error dav1d_new_decoder(void** dec) dav1d_default_settings(&decoder->settings); - decoder->settings.frame_size_limit = MAX_IMAGE_WIDTH * MAX_IMAGE_HEIGHT; + if (heif_get_global_security_limits()->max_image_size_pixels > std::numeric_limits::max()) { + decoder->settings.frame_size_limit = 0; + } + else { + decoder->settings.frame_size_limit = static_cast(heif_get_global_security_limits()->max_image_size_pixels); + } + decoder->settings.all_layers = 0; if (dav1d_open(&decoder->context, &decoder->settings) != 0) { @@ -260,7 +268,7 @@ struct heif_error dav1d_decode_image(void* decoder_raw, struct heif_image** out_ const uint8_t* data = (uint8_t*) frame.data[c]; int stride = (int) frame.stride[c > 0 ? 1 : 0]; - int w, h; + uint32_t w, h; get_subsampled_size(frame.p.w, frame.p.h, channel2plane[c], chroma, &w, &h); @@ -275,7 +283,7 @@ struct heif_error dav1d_decode_image(void* decoder_raw, struct heif_image** out_ int bytes_per_pixel = (bpp + 7) / 8; - for (int y = 0; y < h; y++) { + for (uint32_t y = 0; y < h; y++) { memcpy(dst_mem + y * dst_stride, data + y * stride, w * bytes_per_pixel); } } diff --git a/libheif/plugins/decoder_ffmpeg.cc b/libheif/plugins/decoder_ffmpeg.cc index abf27aeecf..c18502d086 100644 --- a/libheif/plugins/decoder_ffmpeg.cc +++ b/libheif/plugins/decoder_ffmpeg.cc @@ -21,46 +21,25 @@ #include "libheif/heif.h" #include "libheif/heif_plugin.h" #include "decoder_ffmpeg.h" +#include "nalu_utils.h" #if defined(HAVE_CONFIG_H) #include "config.h" #endif -#include + +#include +#include extern "C" { #include } -class NalUnit { -public: - NalUnit(); - ~NalUnit(); - bool set_data(const unsigned char* in_data, int n); - int size() const { return nal_data_size; } - int unit_type() const { return nal_unit_type; } - const unsigned char* data() const { return nal_data_ptr; } - int bitExtracted(int number, int bits_count, int position_nr) - { - return (((1 << bits_count) - 1) & (number >> (position_nr - 1))); - } -private: - const unsigned char* nal_data_ptr; - int nal_unit_type; - int nal_data_size; -}; struct ffmpeg_decoder { - #define NAL_UNIT_VPS_NUT 32 - #define NAL_UNIT_SPS_NUT 33 - #define NAL_UNIT_PPS_NUT 34 - #define NAL_UNIT_IDR_W_RADL 19 - #define NAL_UNIT_IDR_N_LP 20 - - std::map NalMap; - + NalMap nalMap; bool strict_decoding = false; }; @@ -126,64 +105,17 @@ void ffmpeg_set_strict_decoding(void* decoder_raw, int flag) decoder->strict_decoding = flag; } -NalUnit::NalUnit() -{ - nal_data_ptr = NULL; - nal_unit_type = 0; - nal_data_size = 0; -} - -NalUnit::~NalUnit() -{ - -} - -bool NalUnit::set_data(const unsigned char* in_data, int n) -{ - nal_data_ptr = in_data; - nal_unit_type = bitExtracted(nal_data_ptr[0], 6, 2); - nal_data_size = n; - return true; -} - -static struct heif_error ffmpeg_v1_push_data(void* decoder_raw, const void* data, size_t size) +static struct heif_error ffmpeg_v1_push_data(void *decoder_raw, const void *data, size_t size) { struct ffmpeg_decoder* decoder = (struct ffmpeg_decoder*) decoder_raw; const uint8_t* cdata = (const uint8_t*) data; - size_t ptr = 0; - while (ptr < size) { - if (4 > size - ptr) { - struct heif_error err = { heif_error_Decoder_plugin_error, - heif_suberror_End_of_data, - "insufficient data" }; - return err; - } - - uint32_t nal_size = (cdata[ptr] << 24) | (cdata[ptr + 1] << 16) | (cdata[ptr + 2] << 8) | (cdata[ptr + 3]); - ptr += 4; - - if (nal_size > size - ptr) { - struct heif_error err = { heif_error_Decoder_plugin_error, - heif_suberror_End_of_data, - "insufficient data" }; - return err; - } - - NalUnit* nal_unit = new NalUnit(); - nal_unit->set_data(cdata + ptr, nal_size); - - NalUnit* old_nal_unit = decoder->NalMap[nal_unit->unit_type()]; - decoder->NalMap[nal_unit->unit_type()] = nal_unit; - delete old_nal_unit; + return decoder->nalMap.parseHevcNalu(cdata, size); +} - ptr += nal_size; - } - return heif_error_success; -} static heif_chroma ffmpeg_get_chroma_format(enum AVPixelFormat pix_fmt) { if (pix_fmt == AV_PIX_FMT_GRAY8) @@ -341,19 +273,19 @@ static struct heif_error ffmpeg_v1_decode_image(void* decoder_raw, const unsigned char* heif_pps_data; const unsigned char* heif_idrpic_data; - if ((decoder->NalMap.count(NAL_UNIT_VPS_NUT) > 0) - && (decoder->NalMap.count(NAL_UNIT_SPS_NUT) > 0) - && (decoder->NalMap.count(NAL_UNIT_PPS_NUT) > 0) + if ((decoder->nalMap.count(NAL_UNIT_VPS_NUT) > 0) + && (decoder->nalMap.count(NAL_UNIT_SPS_NUT) > 0) + && (decoder->nalMap.count(NAL_UNIT_PPS_NUT) > 0) ) { - heif_vps_size = decoder->NalMap[NAL_UNIT_VPS_NUT]->size(); - heif_vps_data = decoder->NalMap[NAL_UNIT_VPS_NUT]->data(); + heif_vps_size = decoder->nalMap.size(NAL_UNIT_VPS_NUT); + heif_vps_data = decoder->nalMap.data(NAL_UNIT_VPS_NUT); - heif_sps_size = decoder->NalMap[NAL_UNIT_SPS_NUT]->size(); - heif_sps_data = decoder->NalMap[NAL_UNIT_SPS_NUT]->data(); + heif_sps_size = decoder->nalMap.size(NAL_UNIT_SPS_NUT); + heif_sps_data = decoder->nalMap.data(NAL_UNIT_SPS_NUT); - heif_pps_size = decoder->NalMap[NAL_UNIT_PPS_NUT]->size(); - heif_pps_data = decoder->NalMap[NAL_UNIT_PPS_NUT]->data(); + heif_pps_size = decoder->nalMap.size(NAL_UNIT_PPS_NUT); + heif_pps_data = decoder->nalMap.data(NAL_UNIT_PPS_NUT); } else { @@ -363,17 +295,17 @@ static struct heif_error ffmpeg_v1_decode_image(void* decoder_raw, return err; } - if ((decoder->NalMap.count(NAL_UNIT_IDR_W_RADL) > 0) || (decoder->NalMap.count(NAL_UNIT_IDR_N_LP) > 0)) + if ((decoder->nalMap.count(NAL_UNIT_IDR_W_RADL) > 0) || (decoder->nalMap.count(NAL_UNIT_IDR_N_LP) > 0)) { - if (decoder->NalMap.count(NAL_UNIT_IDR_W_RADL) > 0) + if (decoder->nalMap.count(NAL_UNIT_IDR_W_RADL) > 0) { - heif_idrpic_data = decoder->NalMap[NAL_UNIT_IDR_W_RADL]->data(); - heif_idrpic_size = decoder->NalMap[NAL_UNIT_IDR_W_RADL]->size(); + heif_idrpic_data = decoder->nalMap.data(NAL_UNIT_IDR_W_RADL); + heif_idrpic_size = decoder->nalMap.size(NAL_UNIT_IDR_W_RADL); } else { - heif_idrpic_data = decoder->NalMap[NAL_UNIT_IDR_N_LP]->data(); - heif_idrpic_size = decoder->NalMap[NAL_UNIT_IDR_N_LP]->size(); + heif_idrpic_data = decoder->nalMap.data(NAL_UNIT_IDR_N_LP); + heif_idrpic_size = decoder->nalMap.size(NAL_UNIT_IDR_N_LP); } } else @@ -414,11 +346,8 @@ static struct heif_error ffmpeg_v1_decode_image(void* decoder_raw, hevc_data_ptr += hevc_AnnexB_StartCode_size; memcpy(hevc_data_ptr, heif_idrpic_data, heif_idrpic_size); - //decoder->NalMap not needed anymore - for (auto current = decoder->NalMap.begin(); current != decoder->NalMap.end(); ++current) { - delete current->second; - } - decoder->NalMap.clear(); + // decoder->NalMap not needed anymore + decoder->nalMap.clear(); const AVCodec* hevc_codec = NULL; AVCodecParserContext* hevc_parser = NULL; diff --git a/libheif/plugins/decoder_jpeg.cc b/libheif/plugins/decoder_jpeg.cc index 49a95a17e8..901bf92214 100644 --- a/libheif/plugins/decoder_jpeg.cc +++ b/libheif/plugins/decoder_jpeg.cc @@ -1,5 +1,5 @@ /* - * AVIF codec. + * JPEG codec. * Copyright (c) 2023 Dirk Farin * * This file is part of libheif. diff --git a/libheif/plugins/decoder_openh264.cc b/libheif/plugins/decoder_openh264.cc new file mode 100644 index 0000000000..a3eb6c60ca --- /dev/null +++ b/libheif/plugins/decoder_openh264.cc @@ -0,0 +1,331 @@ +/* + * openh264 codec. + * Copyright (c) 2024 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 "libheif/heif.h" +#include "libheif/heif_plugin.h" +#include "decoder_openh264.h" +#include +#include +#include +#include +#include + +#include + + +struct openh264_decoder +{ + std::vector data; +}; + +static const char kSuccess[] = "Success"; + +static const int OpenH264_PLUGIN_PRIORITY = 100; + +#define MAX_PLUGIN_NAME_LENGTH 80 + +static char plugin_name[MAX_PLUGIN_NAME_LENGTH]; + +static heif_error kError_EOF = {heif_error_Decoder_plugin_error, heif_suberror_End_of_data, "Insufficient input data"}; + + +static const char* openh264_plugin_name() +{ + OpenH264Version version = WelsGetCodecVersion(); + + sprintf(plugin_name, "OpenH264 %d.%d.%d", version.uMajor, version.uMinor, version.uRevision); + + return plugin_name; +} + + +static void openh264_init_plugin() +{ +} + + +static void openh264_deinit_plugin() +{ +} + + +static int openh264_does_support_format(enum heif_compression_format format) +{ + if (format == heif_compression_AVC) { + return OpenH264_PLUGIN_PRIORITY; + } + else { + return 0; + } +} + + +struct heif_error openh264_new_decoder(void** dec) +{ + auto* decoder = new openh264_decoder(); + *dec = decoder; + + struct heif_error err = {heif_error_Ok, heif_suberror_Unspecified, kSuccess}; + return err; +} + + +void openh264_free_decoder(void* decoder_raw) +{ + auto* decoder = (openh264_decoder*) decoder_raw; + + if (!decoder) { + return; + } + + delete decoder; +} + + +void openh264_set_strict_decoding(void* decoder_raw, int flag) +{ +// auto* decoder = (openh264_decoder*) decoder_raw; +} + + +struct heif_error openh264_push_data(void* decoder_raw, const void* frame_data, size_t frame_size) +{ + auto* decoder = (struct openh264_decoder*) decoder_raw; + + const auto* input_data = (const uint8_t*) frame_data; + + decoder->data.insert(decoder->data.end(), input_data, input_data + frame_size); + + struct heif_error err = {heif_error_Ok, heif_suberror_Unspecified, kSuccess}; + return err; +} + + +struct heif_error openh264_decode_image(void* decoder_raw, struct heif_image** out_img) +{ + auto* decoder = (struct openh264_decoder*) decoder_raw; + + if (decoder->data.size() < 4) { + return kError_EOF; + } + + const std::vector& indata = decoder->data; + std::vector scdata; + + size_t idx = 0; + while (idx < indata.size()) { + if (indata.size() - 4 < idx) { + return kError_EOF; + } + + uint32_t size = ((indata[idx] << 24) | (indata[idx + 1] << 16) | (indata[idx + 2] << 8) | indata[idx + 3]); + idx += 4; + + if (indata.size() < size || indata.size() - size < idx) { + return kError_EOF; + } + + scdata.push_back(0); + scdata.push_back(0); + scdata.push_back(1); + + // check for need of start code emulation prevention + + bool do_start_code_emulation_check = true; + + while (do_start_code_emulation_check && size >= 3) { + + bool found_start_code_emulation = false; + + for (size_t i = 0; i < size - 3; i++) { + if (indata[idx + 0] == 0 && + indata[idx + 1] == 0 && + (indata[idx + 2] >= 0 && indata[idx + 2] <= 3)) { + scdata.push_back(0); + scdata.push_back(0); + scdata.push_back(3); + + scdata.insert(scdata.end(), &indata[idx + 2], &indata[idx + i + 2]); + idx += i + 2; + size -= (uint32_t)(i + 2); + found_start_code_emulation = true; + break; + } + } + + do_start_code_emulation_check = found_start_code_emulation; + } + + assert(size > 0); + scdata.insert(scdata.end(), &indata[idx], &indata[idx + size]); + + idx += size; + } + + if (idx != indata.size()) { + return kError_EOF; + } + + // input: encoded bitstream start position; should include start code prefix + unsigned char* pBuf = scdata.data(); + + // input: encoded bit stream length; should include the size of start code prefix + int iSize = static_cast(scdata.size()); + + //output: [0~2] for Y,U,V buffer for Decoding only + unsigned char* pData[3] = {nullptr, nullptr, nullptr}; + + // in-out: for Decoding only: declare and initialize the output buffer info, this should never co-exist with Parsing only + + SBufferInfo sDstBufInfo; + memset(&sDstBufInfo, 0, sizeof(SBufferInfo)); + + // Step 2:decoder creation + ISVCDecoder* pSvcDecoder; + WelsCreateDecoder(&pSvcDecoder); + if (!pSvcDecoder) { + return {heif_error_Decoder_plugin_error, + heif_suberror_Unspecified, + "Cannot create OpenH264 decoder"}; + } + + std::unique_ptr dummy_h264_decoder_ptr(pSvcDecoder, WelsDestroyDecoder); + + + // Step 3:declare required parameter, used to differentiate Decoding only and Parsing only + SDecodingParam sDecParam{}; + sDecParam.sVideoProperty.eVideoBsType = VIDEO_BITSTREAM_AVC; + + //for Parsing only, the assignment is mandatory + // sDecParam.bParseOnly = true; + + // Step 4:initialize the parameter and decoder context, allocate memory + pSvcDecoder->Initialize(&sDecParam); + + // Step 5:do actual decoding process in slice level; this can be done in a loop until data ends + + //for Decoding only + int iRet = pSvcDecoder->DecodeFrameNoDelay(pBuf, iSize, pData, &sDstBufInfo); + + if (iRet != 0) { + return {heif_error_Decoder_plugin_error, + heif_suberror_Unspecified, + "OpenH264 decoder error"}; + } + + /* + // TODO: I receive an iBufferStatus==0, but the output image is still decoded + if (sDstBufInfo.iBufferStatus == 0) { + return {heif_error_Decoder_plugin_error, + heif_suberror_Unspecified, + "OpenH264 decoder did not output any image"}; + } + */ + + uint32_t width = sDstBufInfo.UsrData.sSystemBuffer.iWidth; + uint32_t height = sDstBufInfo.UsrData.sSystemBuffer.iHeight; + + struct heif_image* heif_img; + struct heif_error err{}; + + uint32_t cwidth, cheight; + + if (sDstBufInfo.UsrData.sSystemBuffer.iFormat == videoFormatI420) { + cwidth = (width + 1) / 2; + cheight = (height + 1) / 2; + + err = heif_image_create(width, height, + heif_colorspace_YCbCr, + heif_chroma_420, + &heif_img); + if (err.code != heif_error_Ok) { + assert(heif_img == nullptr); + return err; + } + + *out_img = heif_img; + + heif_image_add_plane(heif_img, heif_channel_Y, width, height, 8); + heif_image_add_plane(heif_img, heif_channel_Cb, cwidth, cheight, 8); + heif_image_add_plane(heif_img, heif_channel_Cr, cwidth, cheight, 8); + + int y_stride; + int cb_stride; + int cr_stride; + uint8_t* py = heif_image_get_plane(heif_img, heif_channel_Y, &y_stride); + uint8_t* pcb = heif_image_get_plane(heif_img, heif_channel_Cb, &cb_stride); + uint8_t* pcr = heif_image_get_plane(heif_img, heif_channel_Cr, &cr_stride); + + int ystride = sDstBufInfo.UsrData.sSystemBuffer.iStride[0]; + int cstride = sDstBufInfo.UsrData.sSystemBuffer.iStride[1]; + + for (uint32_t y = 0; y < height; y++) { + memcpy(py + y * y_stride, sDstBufInfo.pDst[0] + y * ystride, width); + } + + for (uint32_t y = 0; y < (height + 1) / 2; y++) { + memcpy(pcb + y * cb_stride, sDstBufInfo.pDst[1] + y * cstride, (width + 1) / 2); + memcpy(pcr + y * cr_stride, sDstBufInfo.pDst[2] + y * cstride, (width + 1) / 2); + } + } + else { + return {heif_error_Decoder_plugin_error, + heif_suberror_Unspecified, + "Unsupported image pixel format"}; + } + + // Step 6:uninitialize the decoder and memory free + + pSvcDecoder->Uninitialize(); // TODO: do we have to Uninitialize when an error is returned? + + decoder->data.clear(); + + return heif_error_ok; +} + + +static const struct heif_decoder_plugin decoder_openh264{ + 3, + openh264_plugin_name, + openh264_init_plugin, + openh264_deinit_plugin, + openh264_does_support_format, + openh264_new_decoder, + openh264_free_decoder, + openh264_push_data, + openh264_decode_image, + openh264_set_strict_decoding, + "openh264" +}; + + +const struct heif_decoder_plugin* get_decoder_plugin_openh264() +{ + return &decoder_openh264; +} + + +#if PLUGIN_OpenH264_DECODER +heif_plugin_info plugin_info { + 1, + heif_plugin_type_decoder, + &decoder_openh264 +}; +#endif diff --git a/libheif/metadata_compression.h b/libheif/plugins/decoder_openh264.h similarity index 64% rename from libheif/metadata_compression.h rename to libheif/plugins/decoder_openh264.h index 3780620b92..7289b6b2fd 100644 --- a/libheif/metadata_compression.h +++ b/libheif/plugins/decoder_openh264.h @@ -1,6 +1,6 @@ /* * HEIF codec. - * Copyright (c) 2022 Dirk Farin + * Copyright (c) 2024 Dirk Farin * * This file is part of libheif. * @@ -17,17 +17,18 @@ * You should have received a copy of the GNU Lesser General Public License * along with libheif. If not, see . */ -#ifndef LIBHEIF_METADATA_COMPRESSION_H -#define LIBHEIF_METADATA_COMPRESSION_H -#include -#include -#include +#ifndef LIBHEIF_DECODER_OPENH264_H +#define LIBHEIF_DECODER_OPENH264_H -#if WITH_DEFLATE_HEADER_COMPRESSION -std::vector deflate(const uint8_t* input, size_t size); +#include "common_utils.h" -std::vector inflate(const std::vector&); +const struct heif_decoder_plugin* get_decoder_plugin_openh264(); + +#if PLUGIN_OpenH264_DECODER +extern "C" { +MAYBE_UNUSED LIBHEIF_API extern heif_plugin_info plugin_info; +} #endif -#endif //LIBHEIF_METADATA_COMPRESSION_H +#endif diff --git a/libheif/plugins/decoder_openjpeg.cc b/libheif/plugins/decoder_openjpeg.cc index 7e801bff00..ee5614bd30 100644 --- a/libheif/plugins/decoder_openjpeg.cc +++ b/libheif/plugins/decoder_openjpeg.cc @@ -27,9 +27,10 @@ #include #include +#include static const int OPENJPEG_PLUGIN_PRIORITY = 100; - +static const int OPENJPEG_PLUGIN_PRIORITY_HTJ2K = 90; struct openjpeg_decoder { @@ -65,6 +66,9 @@ static int openjpeg_does_support_format(enum heif_compression_format format) if (format == heif_compression_JPEG2000) { return OPENJPEG_PLUGIN_PRIORITY; } + else if (format == heif_compression_HTJ2K) { + return OPENJPEG_PLUGIN_PRIORITY_HTJ2K; + } else { return 0; } @@ -251,16 +255,16 @@ opj_stream_t* opj_stream_create_default_memory_stream(openjpeg_decoder* p_decode struct heif_error openjpeg_decode_image(void* decoder_raw, struct heif_image** out_img) { - struct openjpeg_decoder* decoder = (struct openjpeg_decoder*) decoder_raw; + auto* decoder = (struct openjpeg_decoder*) decoder_raw; OPJ_BOOL success; opj_dparameters_t decompression_parameters; - opj_codec_t* l_codec; + std::unique_ptr l_codec(opj_create_decompress(OPJ_CODEC_J2K), + opj_destroy_codec); // Initialize Decoder opj_set_default_decoder_parameters(&decompression_parameters); - l_codec = opj_create_decompress(OPJ_CODEC_J2K); - success = opj_setup_decoder(l_codec, &decompression_parameters); + success = opj_setup_decoder(l_codec.get(), &decompression_parameters); if (!success) { struct heif_error err = {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "opj_setup_decoder()"}; return err; @@ -270,17 +274,21 @@ struct heif_error openjpeg_decode_image(void* decoder_raw, struct heif_image** o // Create Input Stream OPJ_BOOL is_read_stream = true; - opj_stream_t* stream = opj_stream_create_default_memory_stream(decoder, is_read_stream); + std::unique_ptr stream(opj_stream_create_default_memory_stream(decoder, is_read_stream), + opj_stream_destroy); // Read Codestream Header - opj_image_t* image = NULL; - success = opj_read_header(stream, l_codec, &image); + opj_image_t* image_ptr = nullptr; + success = opj_read_header(stream.get(), l_codec.get(), &image_ptr); if (!success) { struct heif_error err = {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "opj_read_header()"}; return err; } - else if (image->numcomps != 3 && image->numcomps != 1) { + + std::unique_ptr image(image_ptr, opj_image_destroy); + + if (image->numcomps != 3 && image->numcomps != 1) { //TODO - Handle other numbers of components struct heif_error err = {heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, "Number of components must be 3 or 1"}; return err; @@ -296,24 +304,20 @@ struct heif_error openjpeg_decode_image(void* decoder_raw, struct heif_image** o /* Get the decoded image */ - success = opj_decode(l_codec, stream, image); + success = opj_decode(l_codec.get(), stream.get(), image.get()); if (!success) { struct heif_error err = {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "opj_decode()"}; return err; } - success = opj_end_decompress(l_codec, stream); + success = opj_end_decompress(l_codec.get(), stream.get()); if (!success) { struct heif_error err = {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "opj_end_decompress()"}; return err; } - /* Close the byte stream */ - opj_stream_destroy(stream); - - heif_colorspace colorspace = heif_colorspace_YCbCr; heif_chroma chroma = heif_chroma_444; //heif_chroma_interleaved_RGB; diff --git a/libheif/plugins/decoder_vvdec.cc b/libheif/plugins/decoder_vvdec.cc index 96fd9df9c3..cc573776f8 100644 --- a/libheif/plugins/decoder_vvdec.cc +++ b/libheif/plugins/decoder_vvdec.cc @@ -28,6 +28,11 @@ #include +#if 0 +#include +#include +#endif + struct vvdec_decoder { @@ -305,6 +310,12 @@ struct heif_error vvdec_decode_image(void* decoder_raw, struct heif_image** out_ for (int y = 0; y < h; y++) { memcpy(dst_mem + y * dst_stride, data + y * stride, w * bytes_per_pixel); } + +#if 0 + std::cout << "DATA " << c << " " << w << " " << h << " bpp:" << bpp << "\n"; + std::cout << write_raw_data_as_hex(dst_mem, w*h, {}, {}); + std::cout << "---\n"; +#endif } *out_img = heif_img; diff --git a/libheif/plugins/encoder_aom.cc b/libheif/plugins/encoder_aom.cc index 445cf9e53c..aeada1e9d7 100644 --- a/libheif/plugins/encoder_aom.cc +++ b/libheif/plugins/encoder_aom.cc @@ -26,6 +26,7 @@ #include #include #include +#include #include #include "encoder_aom.h" @@ -70,6 +71,7 @@ struct encoder_struct_aom int threads; bool lossless; bool lossless_alpha; + bool auto_tiles; #if defined(HAVE_AOM_CODEC_SET_OPTION) std::vector custom_options; @@ -158,6 +160,7 @@ static const char* kParam_alpha_quality = "alpha-quality"; static const char* kParam_alpha_min_q = "alpha-min-q"; static const char* kParam_alpha_max_q = "alpha-max-q"; static const char* kParam_lossless_alpha = "lossless-alpha"; +static const char* kParam_auto_tiles = "auto-tiles"; static const char* kParam_threads = "threads"; static const char* kParam_realtime = "realtime"; static const char* kParam_speed = "speed"; @@ -196,7 +199,7 @@ static const char* aom_plugin_name() } -#define MAX_NPARAMETERS 14 +#define MAX_NPARAMETERS 15 static struct heif_encoder_parameter aom_encoder_params[MAX_NPARAMETERS]; static const struct heif_encoder_parameter* aom_encoder_parameter_ptrs[MAX_NPARAMETERS + 1]; @@ -237,11 +240,17 @@ static void aom_init_parameters() p->version = 2; p->name = kParam_threads; p->type = heif_encoder_parameter_type_integer; - p->integer.default_value = 4; p->has_default = true; p->integer.have_minimum_maximum = true; p->integer.minimum = 1; - p->integer.maximum = 16; + p->integer.maximum = 64; + int threads = static_cast(std::thread::hardware_concurrency()); + if (threads == 0) { + // Could not autodetect, use previous default value. + threads = 4; + } + threads = std::min(threads, p->integer.maximum); + p->integer.default_value = threads; p->integer.valid_values = NULL; p->integer.num_valid_values = 0; d[i++] = p++; @@ -355,6 +364,14 @@ static void aom_init_parameters() p->has_default = true; d[i++] = p++; + assert(i < MAX_NPARAMETERS); + p->version = 2; + p->name = kParam_auto_tiles; + p->type = heif_encoder_parameter_type_boolean; + p->boolean.default_value = false; + p->has_default = true; + d[i++] = p++; + assert(i < MAX_NPARAMETERS + 1); d[i++] = nullptr; } @@ -564,6 +581,9 @@ struct heif_error aom_set_parameter_boolean(void* encoder_raw, const char* name, encoder->alpha_min_q_set = true; } return heif_error_ok; + } else if (strcmp(name, kParam_auto_tiles) == 0) { + encoder->auto_tiles = value; + return heif_error_ok; } set_value(kParam_realtime, realtime_mode); @@ -757,8 +777,6 @@ struct heif_error aom_encode_image(void* encoder_raw, const struct heif_image* i // --- copy libheif image to aom image - aom_image_t input_image; - aom_img_fmt_t img_format = AOM_IMG_FMT_NONE; int chroma_height = 0; @@ -792,8 +810,10 @@ struct heif_error aom_encode_image(void* encoder_raw, const struct heif_image* i img_format = (aom_img_fmt_t) (img_format | AOM_IMG_FMT_HIGHBITDEPTH); } - if (!aom_img_alloc(&input_image, img_format, - source_width, source_height, 1)) { + std::unique_ptr input_image(aom_img_alloc(nullptr, img_format, + source_width, source_height, 1), + aom_img_free); + if (!input_image) { err = {heif_error_Memory_allocation_error, heif_suberror_Unspecified, "Failed to allocate image"}; @@ -802,8 +822,8 @@ struct heif_error aom_encode_image(void* encoder_raw, const struct heif_image* i for (int plane = 0; plane < 3; plane++) { - unsigned char* buf = input_image.planes[plane]; - const int stride = input_image.stride[plane]; + unsigned char* buf = input_image->planes[plane]; + const int stride = input_image->stride[plane]; if (chroma == heif_chroma_monochrome && plane != 0) { if (bpp_y == 8) { @@ -973,6 +993,11 @@ struct heif_error aom_encode_image(void* encoder_raw, const struct heif_image* i #endif } +#if defined(AOM_CTRL_AV1E_SET_AUTO_TILES) + // aom 3.10.0 + aom_codec_control(&codec, AV1E_SET_AUTO_TILES, encoder->auto_tiles); +#endif + // TODO: set AV1E_SET_TILE_ROWS and AV1E_SET_TILE_COLUMNS. @@ -1021,15 +1046,11 @@ struct heif_error aom_encode_image(void* encoder_raw, const struct heif_image* i // --- encode frame - res = aom_codec_encode(&codec, &input_image, + res = aom_codec_encode(&codec, input_image.get(), 0, // only encoding a single frame 1, 0); // no flags - // Note: we are freeing the input image directly after use. - // This covers the usual success case and also all error cases that occur below. - aom_img_free(&input_image); - if (res != AOM_CODEC_OK) { err = { heif_error_Encoder_plugin_error, diff --git a/libheif/plugins/encoder_openjpeg.cc b/libheif/plugins/encoder_openjpeg.cc index eadd5870b5..304f2f391e 100644 --- a/libheif/plugins/encoder_openjpeg.cc +++ b/libheif/plugins/encoder_openjpeg.cc @@ -577,6 +577,9 @@ void opj_query_encoded_size(void* encoder, uint32_t input_width, uint32_t input_ // of required rounding, or a required minimum size. Use this function to return // the encoded size for a given input image size. // You may set this to NULL if no padding is required for any image size. + + *encoded_width = input_width; + *encoded_height = input_height; } diff --git a/libheif/plugins/encoder_uvg266.cc b/libheif/plugins/encoder_uvg266.cc index 812c3b4830..80864188c5 100644 --- a/libheif/plugins/encoder_uvg266.cc +++ b/libheif/plugins/encoder_uvg266.cc @@ -48,7 +48,7 @@ struct encoder_struct_uvg266 size_t output_idx = 0; }; -static const int uvg266_PLUGIN_PRIORITY = 100; +static const int uvg266_PLUGIN_PRIORITY = 50; #define MAX_PLUGIN_NAME_LENGTH 80 diff --git a/libheif/plugins/encoder_vvenc.cc b/libheif/plugins/encoder_vvenc.cc new file mode 100644 index 0000000000..a7eee6a490 --- /dev/null +++ b/libheif/plugins/encoder_vvenc.cc @@ -0,0 +1,723 @@ +/* + * HEIF codec. + * Copyright (c) 2023 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 "libheif/heif.h" +#include "libheif/heif_plugin.h" +#include "encoder_vvenc.h" +#include +#include // apparently, this is a false positive of cpplint +#include +#include +#include +#include + +extern "C" { +#include +} + + +// TODO: it seems that the encoder does not support monochrome input. This affects also images with alpha channels. + +static const char* kError_unspecified_error = "Unspecified encoder error"; +static const char* kError_unsupported_bit_depth = "Bit depth not supported by vvenc"; +static const char* kError_unsupported_chroma = "Unsupported chroma type"; +//static const char* kError_unsupported_image_size = "Images smaller than 16 pixels are not supported"; + + +struct encoder_struct_vvenc +{ + int quality = 32; + bool lossless = false; + + std::vector output_data; + size_t output_idx = 0; +}; + +static const int vvenc_PLUGIN_PRIORITY = 100; + +#define MAX_PLUGIN_NAME_LENGTH 80 + +static char plugin_name[MAX_PLUGIN_NAME_LENGTH]; + + +static void vvenc_set_default_parameters(void* encoder); + + +static const char* vvenc_plugin_name() +{ + strcpy(plugin_name, "vvenc VVC encoder"); + return plugin_name; +} + + +#define MAX_NPARAMETERS 10 + +static struct heif_encoder_parameter vvenc_encoder_params[MAX_NPARAMETERS]; +static const struct heif_encoder_parameter* vvenc_encoder_parameter_ptrs[MAX_NPARAMETERS + 1]; + +static void vvenc_init_parameters() +{ + struct heif_encoder_parameter* p = vvenc_encoder_params; + const struct heif_encoder_parameter** d = vvenc_encoder_parameter_ptrs; + int i = 0; + + assert(i < MAX_NPARAMETERS); + p->version = 2; + p->name = heif_encoder_parameter_name_quality; + p->type = heif_encoder_parameter_type_integer; + p->integer.default_value = 50; + p->has_default = true; + p->integer.have_minimum_maximum = true; + p->integer.minimum = 0; + p->integer.maximum = 100; + p->integer.valid_values = NULL; + p->integer.num_valid_values = 0; + d[i++] = p++; + + assert(i < MAX_NPARAMETERS); + p->version = 2; + p->name = heif_encoder_parameter_name_lossless; + p->type = heif_encoder_parameter_type_boolean; + p->boolean.default_value = false; + p->has_default = true; + d[i++] = p++; + + d[i++] = nullptr; +} + + +const struct heif_encoder_parameter** vvenc_list_parameters(void* encoder) +{ + return vvenc_encoder_parameter_ptrs; +} + + +static void vvenc_init_plugin() +{ + vvenc_init_parameters(); +} + + +static void vvenc_cleanup_plugin() +{ +} + + +static struct heif_error vvenc_new_encoder(void** enc) +{ + struct encoder_struct_vvenc* encoder = new encoder_struct_vvenc(); + struct heif_error err = heif_error_ok; + + *enc = encoder; + + // set default parameters + + vvenc_set_default_parameters(encoder); + + return err; +} + +static void vvenc_free_encoder(void* encoder_raw) +{ + struct encoder_struct_vvenc* encoder = (struct encoder_struct_vvenc*) encoder_raw; + + delete encoder; +} + +static struct heif_error vvenc_set_parameter_quality(void* encoder_raw, int quality) +{ + struct encoder_struct_vvenc* encoder = (struct encoder_struct_vvenc*) encoder_raw; + + if (quality < 0 || quality > 100) { + return heif_error_invalid_parameter_value; + } + + encoder->quality = quality; + + return heif_error_ok; +} + +static struct heif_error vvenc_get_parameter_quality(void* encoder_raw, int* quality) +{ + struct encoder_struct_vvenc* encoder = (struct encoder_struct_vvenc*) encoder_raw; + + *quality = encoder->quality; + + return heif_error_ok; +} + +static struct heif_error vvenc_set_parameter_lossless(void* encoder_raw, int enable) +{ + struct encoder_struct_vvenc* encoder = (struct encoder_struct_vvenc*) encoder_raw; + + encoder->lossless = enable ? 1 : 0; + + return heif_error_ok; +} + +static struct heif_error vvenc_get_parameter_lossless(void* encoder_raw, int* enable) +{ + struct encoder_struct_vvenc* encoder = (struct encoder_struct_vvenc*) encoder_raw; + + *enable = encoder->lossless; + + return heif_error_ok; +} + +static struct heif_error vvenc_set_parameter_logging_level(void* encoder_raw, int logging) +{ +// struct encoder_struct_vvenc* encoder = (struct encoder_struct_vvenc*) encoder_raw; + +// return heif_error_invalid_parameter_value; + + return heif_error_ok; +} + +static struct heif_error vvenc_get_parameter_logging_level(void* encoder_raw, int* loglevel) +{ +// struct encoder_struct_vvenc* encoder = (struct encoder_struct_vvenc*) encoder_raw; + + *loglevel = 0; + + return heif_error_ok; +} + + +static struct heif_error vvenc_set_parameter_integer(void* encoder_raw, const char* name, int value) +{ + struct encoder_struct_vvenc* encoder = (struct encoder_struct_vvenc*) encoder_raw; + + if (strcmp(name, heif_encoder_parameter_name_quality) == 0) { + return vvenc_set_parameter_quality(encoder, value); + } + else if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) { + return vvenc_set_parameter_lossless(encoder, value); + } + + return heif_error_unsupported_parameter; +} + +static struct heif_error vvenc_get_parameter_integer(void* encoder_raw, const char* name, int* value) +{ + struct encoder_struct_vvenc* encoder = (struct encoder_struct_vvenc*) encoder_raw; + + if (strcmp(name, heif_encoder_parameter_name_quality) == 0) { + return vvenc_get_parameter_quality(encoder, value); + } + else if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) { + return vvenc_get_parameter_lossless(encoder, value); + } + + return heif_error_unsupported_parameter; +} + + +static struct heif_error vvenc_set_parameter_boolean(void* encoder, const char* name, int value) +{ + if (strcmp(name, heif_encoder_parameter_name_lossless) == 0) { + return vvenc_set_parameter_lossless(encoder, value); + } + + return heif_error_unsupported_parameter; +} + +// Unused, will use "vvenc_get_parameter_integer" instead. +/* +static struct heif_error vvenc_get_parameter_boolean(void* encoder, const char* name, int* value) +{ + if (strcmp(name, heif_encoder_parameter_name_lossless)==0) { + return vvenc_get_parameter_lossless(encoder,value); + } + + return heif_error_unsupported_parameter; +} +*/ + + +static struct heif_error vvenc_set_parameter_string(void* encoder_raw, const char* name, const char* value) +{ + return heif_error_unsupported_parameter; +} + +static struct heif_error vvenc_get_parameter_string(void* encoder_raw, const char* name, + char* value, int value_size) +{ + return heif_error_unsupported_parameter; +} + + +static void vvenc_set_default_parameters(void* encoder) +{ + for (const struct heif_encoder_parameter** p = vvenc_encoder_parameter_ptrs; *p; p++) { + const struct heif_encoder_parameter* param = *p; + + if (param->has_default) { + switch (param->type) { + case heif_encoder_parameter_type_integer: + vvenc_set_parameter_integer(encoder, param->name, param->integer.default_value); + break; + case heif_encoder_parameter_type_boolean: + vvenc_set_parameter_boolean(encoder, param->name, param->boolean.default_value); + break; + case heif_encoder_parameter_type_string: + vvenc_set_parameter_string(encoder, param->name, param->string.default_value); + break; + } + } + } +} + + +static void vvenc_query_input_colorspace(heif_colorspace* colorspace, heif_chroma* chroma) +{ + if (*colorspace == heif_colorspace_monochrome) { + *colorspace = heif_colorspace_monochrome; + *chroma = heif_chroma_monochrome; + } + else { + *colorspace = heif_colorspace_YCbCr; + *chroma = heif_chroma_420; + } +} + + +static void vvenc_query_input_colorspace2(void* encoder_raw, heif_colorspace* colorspace, heif_chroma* chroma) +{ + if (*colorspace == heif_colorspace_monochrome) { + *colorspace = heif_colorspace_monochrome; + *chroma = heif_chroma_monochrome; + } + else { + *colorspace = heif_colorspace_YCbCr; + if (*chroma != heif_chroma_420 && + *chroma != heif_chroma_422 && + *chroma != heif_chroma_444) { + *chroma = heif_chroma_420; + } + } +} + +void vvenc_query_encoded_size(void* encoder_raw, uint32_t input_width, uint32_t input_height, + uint32_t* encoded_width, uint32_t* encoded_height) +{ + *encoded_width = (input_width + 7) & ~0x7; + *encoded_height = (input_height + 7) & ~0x7; +} + + +#include +#include + +static void append_chunk_data(struct encoder_struct_vvenc* encoder, vvencAccessUnit* au) +{ +#if 0 + std::cout << "DATA\n"; + std::cout << write_raw_data_as_hex(au->payload, au->payloadUsedSize, {}, {}); + std::cout << "---\n"; +#endif + + size_t old_size = encoder->output_data.size(); + encoder->output_data.resize(old_size + au->payloadUsedSize); + memcpy(encoder->output_data.data() + old_size, au->payload, au->payloadUsedSize); +} + + +static void copy_plane(int16_t*& out_p, int& out_stride, const uint8_t* in_p, uint32_t in_stride, int w, int h, int padded_width, int padded_height) +{ + out_stride = padded_width; + out_p = new int16_t[out_stride * w * h]; + + for (int y = 0; y < padded_height; y++) { + int sy = std::min(y, h - 1); // source y + + for (int x = 0; x < w; x++) { + out_p[y * out_stride + x] = in_p[sy * in_stride + x]; + } + + for (int x = w; x < padded_width; x++) { + out_p[y * out_stride + x] = in_p[sy * in_stride + w - 1]; + } + } +} + + +static struct heif_error vvenc_encode_image(void* encoder_raw, const struct heif_image* image, + heif_image_input_class input_class) +{ + struct encoder_struct_vvenc* encoder = (struct encoder_struct_vvenc*) encoder_raw; + + int bit_depth = heif_image_get_bits_per_pixel_range(image, heif_channel_Y); + bool isGreyscale = (heif_image_get_colorspace(image) == heif_colorspace_monochrome); + heif_chroma chroma = heif_image_get_chroma_format(image); + + if (bit_depth != 8) { + return heif_error{ + heif_error_Encoder_plugin_error, + heif_suberror_Unsupported_image_type, + kError_unsupported_bit_depth + }; + } + + + int input_width = heif_image_get_width(image, heif_channel_Y); + int input_height = heif_image_get_height(image, heif_channel_Y); + + int input_chroma_width = 0; + int input_chroma_height = 0; + + uint32_t encoded_width, encoded_height; + vvenc_query_encoded_size(encoder_raw, input_width, input_height, &encoded_width, &encoded_height); + + vvencChromaFormat vvencChroma; + int chroma_stride_shift = 0; + int chroma_height_shift = 0; + + if (isGreyscale) { + vvencChroma = VVENC_CHROMA_400; + } + else if (chroma == heif_chroma_420) { + vvencChroma = VVENC_CHROMA_420; + chroma_stride_shift = 1; + chroma_height_shift = 1; + input_chroma_width = (input_width + 1) / 2; + input_chroma_height = (input_height + 1) / 2; + } + else if (chroma == heif_chroma_422) { + vvencChroma = VVENC_CHROMA_422; + chroma_stride_shift = 1; + chroma_height_shift = 0; + input_chroma_width = (input_width + 1) / 2; + input_chroma_height = input_height; + } + else if (chroma == heif_chroma_444) { + vvencChroma = VVENC_CHROMA_444; + chroma_stride_shift = 0; + chroma_height_shift = 0; + input_chroma_width = input_width; + input_chroma_height = input_height; + } + else { + return heif_error{ + heif_error_Encoder_plugin_error, + heif_suberror_Unsupported_image_type, + kError_unsupported_chroma + }; + } + + if (chroma != heif_chroma_monochrome) { + int w = heif_image_get_width(image, heif_channel_Y); + int h = heif_image_get_height(image, heif_channel_Y); + if (chroma != heif_chroma_444) { w = (w + 1) / 2; } + if (chroma == heif_chroma_420) { h = (h + 1) / 2; } + + assert(heif_image_get_width(image, heif_channel_Cb) == w); + assert(heif_image_get_width(image, heif_channel_Cr) == w); + assert(heif_image_get_height(image, heif_channel_Cb) == h); + assert(heif_image_get_height(image, heif_channel_Cr) == h); + (void) w; + (void) h; + } + + + vvenc_config params; + + // invert encoder quality range and scale to 0-63 + int encoder_quality = 63 - encoder->quality*63/100; + + int ret = vvenc_init_default(¶ms, encoded_width, encoded_height, 25, 0, + encoder_quality, + VVENC_MEDIUM); + if (ret != VVENC_OK) { + // TODO: cleanup memory + + return heif_error{ + heif_error_Encoder_plugin_error, + heif_suberror_Encoder_encoding, + kError_unspecified_error + }; + } + + params.m_inputBitDepth[0] = bit_depth; + params.m_inputBitDepth[1] = bit_depth; + params.m_outputBitDepth[0] = bit_depth; + params.m_outputBitDepth[1] = bit_depth; + params.m_internalBitDepth[0] = bit_depth; + params.m_internalBitDepth[1] = bit_depth; + + vvencEncoder* vvencoder = vvenc_encoder_create(); + ret = vvenc_encoder_open(vvencoder, ¶ms); + if (ret != VVENC_OK) { + // TODO: cleanup memory + + return heif_error{ + heif_error_Encoder_plugin_error, + heif_suberror_Encoder_encoding, + kError_unspecified_error + }; + } + + + struct heif_color_profile_nclx* nclx = nullptr; + heif_error err = heif_image_get_nclx_color_profile(image, &nclx); + if (err.code != heif_error_Ok) { + nclx = nullptr; + } + + // make sure NCLX profile is deleted at end of function + auto nclx_deleter = std::unique_ptr(nclx, heif_nclx_color_profile_free); + +#if 0 + if (nclx) { + config->vui.fullrange = nclx->full_range_flag; + } + else { + config->vui.fullrange = 1; + } + + if (nclx && + (input_class == heif_image_input_class_normal || + input_class == heif_image_input_class_thumbnail)) { + config->vui.colorprim = nclx->color_primaries; + config->vui.transfer = nclx->transfer_characteristics; + config->vui.colormatrix = nclx->matrix_coefficients; + } + + config->qp = ((100 - encoder->quality) * 51 + 50) / 100; + config->lossless = encoder->lossless ? 1 : 0; + + config->width = encoded_width; + config->height = encoded_height; +#endif + + // Note: it is ok to cast away the const, as the image content is not changed. + // However, we have to guarantee that there are no plane pointers or stride values kept over calling the svt_encode_image() function. + /* + err = heif_image_extend_padding_to_size(const_cast(image), + param->sourceWidth, + param->sourceHeight); + if (err.code) { + return err; + } +*/ + + vvencYUVBuffer* yuvbuf = vvenc_YUVBuffer_alloc(); + vvenc_YUVBuffer_alloc_buffer(yuvbuf, vvencChroma, encoded_width, encoded_height); + + vvencAccessUnit* au = vvenc_accessUnit_alloc(); + + const int auSizeScale = (vvencChroma <= VVENC_CHROMA_420 ? 2 : 3); + vvenc_accessUnit_alloc_payload(au, auSizeScale * encoded_width * encoded_height + 1024); + + // vvenc_init_pass( encoder, pass, statsfilename ); + + int16_t* yptr = nullptr; + int16_t* cbptr = nullptr; + int16_t* crptr = nullptr; + int ystride = 0; + int cbstride = 0; + int crstride = 0; + + if (isGreyscale) { + int stride; + const uint8_t* data = heif_image_get_plane_readonly(image, heif_channel_Y, &stride); + + copy_plane(yptr, ystride, data, stride, input_width, input_height, encoded_width, encoded_height); + + yuvbuf->planes[0].ptr = yptr; + yuvbuf->planes[0].width = encoded_width; + yuvbuf->planes[0].height = encoded_height; + yuvbuf->planes[0].stride = ystride; + } + else { + int stride; + const uint8_t* data; + + data = heif_image_get_plane_readonly(image, heif_channel_Y, &stride); + copy_plane(yptr, ystride, data, stride, input_width, input_height, encoded_width, encoded_height); + + data = heif_image_get_plane_readonly(image, heif_channel_Cb, &stride); + copy_plane(cbptr, cbstride, data, stride, input_chroma_width, input_chroma_height, + encoded_width >> chroma_stride_shift, encoded_height >> chroma_height_shift); + + data = heif_image_get_plane_readonly(image, heif_channel_Cr, &stride); + copy_plane(crptr, crstride, data, stride, input_chroma_width, input_chroma_height, + encoded_width >> chroma_stride_shift, encoded_height >> chroma_height_shift); + + yuvbuf->planes[0].ptr = yptr; + yuvbuf->planes[0].width = encoded_width; + yuvbuf->planes[0].height = encoded_height; + yuvbuf->planes[0].stride = ystride; + + yuvbuf->planes[1].ptr = cbptr; + yuvbuf->planes[1].width = encoded_width >> chroma_stride_shift; + yuvbuf->planes[1].height = encoded_height >> chroma_height_shift; + yuvbuf->planes[1].stride = cbstride; + + yuvbuf->planes[2].ptr = crptr; + yuvbuf->planes[2].width = encoded_width >> chroma_stride_shift; + yuvbuf->planes[2].height = encoded_height >> chroma_height_shift; + yuvbuf->planes[2].stride = crstride; + } + + //yuvbuf->cts = frame->pts; + //yuvbuf->ctsValid = true; + + + bool encDone; + + ret = vvenc_encode(vvencoder, yuvbuf, au, &encDone); + if (ret != VVENC_OK) { + vvenc_encoder_close(vvencoder); + vvenc_YUVBuffer_free(yuvbuf, true); // release storage and payload memory + vvenc_accessUnit_free(au, true); // release storage and payload memory + + return heif_error{ + heif_error_Encoder_plugin_error, + heif_suberror_Encoder_encoding, + kError_unspecified_error + }; + } + + if (au->payloadUsedSize > 0) { + append_chunk_data(encoder, au); + } + + while (!encDone) { + ret = vvenc_encode(vvencoder, nullptr, au, &encDone); + if (ret != VVENC_OK) { + vvenc_encoder_close(vvencoder); + vvenc_YUVBuffer_free(yuvbuf, true); // release storage and payload memory + vvenc_accessUnit_free(au, true); // release storage and payload memory + + return heif_error{ + heif_error_Encoder_plugin_error, + heif_suberror_Encoder_encoding, + kError_unspecified_error + }; + } + + if (au->payloadUsedSize > 0) { + append_chunk_data(encoder, au); + } + } + + vvenc_encoder_close(vvencoder); + vvenc_YUVBuffer_free(yuvbuf, true); // release storage and payload memory + vvenc_accessUnit_free(au, true); // release storage and payload memory + + /* + delete[] yptr; + delete[] cbptr; + delete[] crptr; +*/ + + return heif_error_ok; +} + + +static struct heif_error vvenc_get_compressed_data(void* encoder_raw, uint8_t** data, int* size, + enum heif_encoded_data_type* type) +{ + struct encoder_struct_vvenc* encoder = (struct encoder_struct_vvenc*) encoder_raw; + + if (encoder->output_idx == encoder->output_data.size()) { + *data = nullptr; + *size = 0; + + return heif_error_ok; + } + + size_t start_idx = encoder->output_idx; + + while (start_idx < encoder->output_data.size() - 3 && + (encoder->output_data[start_idx] != 0 || + encoder->output_data[start_idx + 1] != 0 || + encoder->output_data[start_idx + 2] != 1)) { + start_idx++; + } + + size_t end_idx = start_idx + 1; + + while (end_idx < encoder->output_data.size() - 3 && + (encoder->output_data[end_idx] != 0 || + encoder->output_data[end_idx + 1] != 0 || + encoder->output_data[end_idx + 2] != 1)) { + end_idx++; + } + + if (end_idx == encoder->output_data.size() - 3) { + end_idx = encoder->output_data.size(); + } + + *data = encoder->output_data.data() + start_idx + 3; + *size = (int) (end_idx - start_idx - 3); + + encoder->output_idx = end_idx; + + return heif_error_ok; +} + + +static const struct heif_encoder_plugin encoder_plugin_vvenc + { + /* plugin_api_version */ 3, + /* compression_format */ heif_compression_VVC, + /* id_name */ "vvenc", + /* priority */ vvenc_PLUGIN_PRIORITY, + /* supports_lossy_compression */ true, + /* supports_lossless_compression */ true, + /* get_plugin_name */ vvenc_plugin_name, + /* init_plugin */ vvenc_init_plugin, + /* cleanup_plugin */ vvenc_cleanup_plugin, + /* new_encoder */ vvenc_new_encoder, + /* free_encoder */ vvenc_free_encoder, + /* set_parameter_quality */ vvenc_set_parameter_quality, + /* get_parameter_quality */ vvenc_get_parameter_quality, + /* set_parameter_lossless */ vvenc_set_parameter_lossless, + /* get_parameter_lossless */ vvenc_get_parameter_lossless, + /* set_parameter_logging_level */ vvenc_set_parameter_logging_level, + /* get_parameter_logging_level */ vvenc_get_parameter_logging_level, + /* list_parameters */ vvenc_list_parameters, + /* set_parameter_integer */ vvenc_set_parameter_integer, + /* get_parameter_integer */ vvenc_get_parameter_integer, + /* set_parameter_boolean */ vvenc_set_parameter_integer, // boolean also maps to integer function + /* get_parameter_boolean */ vvenc_get_parameter_integer, // boolean also maps to integer function + /* set_parameter_string */ vvenc_set_parameter_string, + /* get_parameter_string */ vvenc_get_parameter_string, + /* query_input_colorspace */ vvenc_query_input_colorspace, + /* encode_image */ vvenc_encode_image, + /* get_compressed_data */ vvenc_get_compressed_data, + /* query_input_colorspace (v2) */ vvenc_query_input_colorspace2, + /* query_encoded_size (v3) */ vvenc_query_encoded_size + }; + +const struct heif_encoder_plugin* get_encoder_plugin_vvenc() +{ + return &encoder_plugin_vvenc; +} + + +#if PLUGIN_VVENC +heif_plugin_info plugin_info { + 1, + heif_plugin_type_encoder, + &encoder_plugin_vvenc +}; +#endif diff --git a/libheif/plugins/encoder_vvenc.h b/libheif/plugins/encoder_vvenc.h new file mode 100644 index 0000000000..bf739dcdc1 --- /dev/null +++ b/libheif/plugins/encoder_vvenc.h @@ -0,0 +1,34 @@ +/* + * HEIF codec. + * Copyright (c) 2023 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_ENCODER_VVENC_H +#define LIBHEIF_ENCODER_VVENC_H + +#include "common_utils.h" + +const struct heif_encoder_plugin* get_encoder_plugin_vvenc(); + +#if PLUGIN_VVENC +extern "C" { +MAYBE_UNUSED LIBHEIF_API extern heif_plugin_info plugin_info; +} +#endif + +#endif diff --git a/libheif/plugins/encoder_x265.cc b/libheif/plugins/encoder_x265.cc index 313b95f835..22db3f5d56 100644 --- a/libheif/plugins/encoder_x265.cc +++ b/libheif/plugins/encoder_x265.cc @@ -901,11 +901,20 @@ static struct heif_error x265_encode_image(void* encoder_raw, const struct heif_ encoder->encoder = api->encoder_open(param); +#if X265_BUILD >= 212 + x265_picture* out_pic = NULL; + api->encoder_encode(encoder->encoder, + &encoder->nals, + &encoder->num_nals, + pic, + &out_pic); +#else api->encoder_encode(encoder->encoder, &encoder->nals, &encoder->num_nals, pic, NULL); +#endif api->picture_free(pic); api->param_free(param); @@ -967,11 +976,20 @@ static struct heif_error x265_get_compressed_data(void* encoder_raw, uint8_t** d encoder->nal_output_counter = 0; +#if X265_BUILD >= 212 + x265_picture* out_pic = NULL; + int result = api->encoder_encode(encoder->encoder, + &encoder->nals, + &encoder->num_nals, + NULL, + &out_pic); +#else int result = api->encoder_encode(encoder->encoder, &encoder->nals, &encoder->num_nals, NULL, NULL); +#endif if (result <= 0) { *data = nullptr; *size = 0; diff --git a/libheif/plugins/nalu_utils.cc b/libheif/plugins/nalu_utils.cc new file mode 100644 index 0000000000..f4f3731d60 --- /dev/null +++ b/libheif/plugins/nalu_utils.cc @@ -0,0 +1,97 @@ +/* + * HEIF codec. + * Copyright (c) 2023 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 +#include +#include "nalu_utils.h" + +NalUnit::NalUnit() +{ + nal_data_ptr = NULL; + nal_unit_type = 0; + nal_data_size = 0; +} + +bool NalUnit::set_data(const unsigned char* in_data, int n) +{ + nal_data_ptr = in_data; + nal_unit_type = bitExtracted(nal_data_ptr[0], 6, 2); + nal_data_size = n; + return true; +} + +int NalUnit::bitExtracted(int number, int bits_count, int position_nr) +{ + return (((1 << bits_count) - 1) & (number >> (position_nr - 1))); +} + +size_t NalMap::count(int nal_type) +{ + return map.count(nal_type); +} + +const unsigned char* NalMap::data(int nal_type) +{ + return map[nal_type]->data(); +} + +int NalMap::size(int nal_type) +{ + return map[nal_type]->size(); +} + +const heif_error NalMap::parseHevcNalu(const uint8_t *cdata, size_t size) +{ + size_t ptr = 0; + while (ptr < size) + { + if (4 > size - ptr) + { + struct heif_error err = {heif_error_Decoder_plugin_error, + heif_suberror_End_of_data, + "insufficient data"}; + return err; + } + + uint32_t nal_size = (cdata[ptr] << 24) | (cdata[ptr + 1] << 16) | (cdata[ptr + 2] << 8) | (cdata[ptr + 3]); + ptr += 4; + + if (nal_size > size - ptr) + { + struct heif_error err = {heif_error_Decoder_plugin_error, + heif_suberror_End_of_data, + "insufficient data"}; + return err; + } + + std::unique_ptr nal_unit = std::unique_ptr(new NalUnit()); + nal_unit->set_data(cdata + ptr, nal_size); + + // overwrite NalMap (frees old NalUnit, if it was set) + map[nal_unit->unit_type()] = std::move(nal_unit); + + ptr += nal_size; + } + + return heif_error_success; +} + +void NalMap::clear() { map.clear(); } \ No newline at end of file diff --git a/libheif/plugins/nalu_utils.h b/libheif/plugins/nalu_utils.h new file mode 100644 index 0000000000..7c4b86f7ad --- /dev/null +++ b/libheif/plugins/nalu_utils.h @@ -0,0 +1,61 @@ +/* + * HEIF codec. + * Copyright (c) 2023 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 +#include "libheif/heif.h" + +static const int NAL_UNIT_VPS_NUT = 32; +static const int NAL_UNIT_SPS_NUT = 33; +static const int NAL_UNIT_PPS_NUT = 34; +static const int NAL_UNIT_IDR_W_RADL = 19; +static const int NAL_UNIT_IDR_N_LP = 20; + +class NalUnit +{ +public: + NalUnit(); + ~NalUnit() = default; + bool set_data(const unsigned char* in_data, int n); + int size() const { return nal_data_size; } + int unit_type() const { return nal_unit_type; } + const unsigned char* data() const { return nal_data_ptr; } + int bitExtracted(int number, int bits_count, int position_nr); +private: + const unsigned char* nal_data_ptr; + int nal_unit_type; + int nal_data_size; +}; + +class NalMap +{ +public: + size_t count(int nal_type); + + const unsigned char* data(int nal_type); + + int size(int nal_type); + + const heif_error parseHevcNalu(const uint8_t *cdata, size_t size); + + void clear(); +private: + std::map> map; +}; \ No newline at end of file diff --git a/libheif/region.cc b/libheif/region.cc index 2260c5d5a4..de7c40e3a0 100644 --- a/libheif/region.cc +++ b/libheif/region.cc @@ -484,7 +484,7 @@ RegionCoordinateTransform RegionCoordinateTransform::create(std::shared_ptr(property); RegionCoordinateTransform tmp; - switch (irot->get_rotation()) { + switch (irot->get_rotation_ccw()) { case 90: tmp.a = transform.c; tmp.b = transform.d; diff --git a/libheif/security_limits.cc b/libheif/security_limits.cc new file mode 100644 index 0000000000..09edb97043 --- /dev/null +++ b/libheif/security_limits.cc @@ -0,0 +1,72 @@ +/* + * HEIF codec. + * Copyright (c) 2024 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 "security_limits.h" +#include + + +struct heif_security_limits global_security_limits { + .version = 1, + + // --- version 1 + + // Artificial limit to avoid allocating too much memory. + // 32768^2 = 1.5 GB as YUV-4:2:0 or 4 GB as RGB32 + .max_image_size_pixels = 32768 * 32768, + .max_number_of_tiles = 4096 * 4096, + .max_bayer_pattern_pixels = 16*16, + .max_items = 1000, + + .max_color_profile_size = 100 * 1024 * 1024, // 100 MB + .max_memory_block_size = 512 * 1024 * 1024, // 512 MB + + .max_iloc_items = 2000, + .max_iloc_extents_per_item = 32, + + .max_children_per_box = 100 +}; + + +Error check_for_valid_image_size(const heif_security_limits* limits, uint32_t width, uint32_t height) +{ + uint64_t maximum_image_size_limit = limits->max_image_size_pixels; + + // --- check whether the image size is "too large" + + auto max_width_height = static_cast(std::numeric_limits::max()); + if ((width > max_width_height || height > max_width_height) || + (height != 0 && width > maximum_image_size_limit / height)) { + std::stringstream sstr; + sstr << "Image size " << width << "x" << height << " exceeds the maximum image size " + << maximum_image_size_limit << "\n"; + + return {heif_error_Memory_allocation_error, + heif_suberror_Security_limit_exceeded, + sstr.str()}; + } + + if (width == 0 || height == 0) { + return {heif_error_Memory_allocation_error, + heif_suberror_Invalid_image_size, + "zero width or height"}; + } + + return Error::Ok; +} diff --git a/libheif/security_limits.h b/libheif/security_limits.h index f1c177c0a1..aece26191e 100644 --- a/libheif/security_limits.h +++ b/libheif/security_limits.h @@ -20,19 +20,13 @@ #ifndef LIBHEIF_SECURITY_LIMITS_H #define LIBHEIF_SECURITY_LIMITS_H +#include "libheif/heif.h" #include #include +#include "error.h" -static const size_t MAX_CHILDREN_PER_BOX = 20000; -static const int MAX_ILOC_ITEMS = 20000; -static const int MAX_ILOC_EXTENTS_PER_ITEM = 32; -static const int MAX_MEMORY_BLOCK_SIZE = 512 * 1024 * 1024; // 512 MB -static const int MAX_COLOR_PROFILE_SIZE = 100 * 1024 * 1024; // 100 MB -// Artificial limit to avoid allocating too much memory. -// 32768^2 = 1.5 GB as YUV-4:2:0 or 4 GB as RGB32 -static const int MAX_IMAGE_WIDTH = 32768; -static const int MAX_IMAGE_HEIGHT = 32768; +extern heif_security_limits global_security_limits; // Maximum nesting level of boxes in input files. // We put a limit on this to avoid unlimited stack usage by malicious input files. @@ -43,6 +37,7 @@ static const int64_t MAX_LARGE_BOX_SIZE = 0x0FFFFFFFFFFFFFFF; static const int64_t MAX_FILE_POS = 0x007FFFFFFFFFFFFFLL; // maximum file position static const int MAX_FRACTION_VALUE = 0x10000; -static const int MAX_IREF_REFERENCES = 10000; + +Error check_for_valid_image_size(const heif_security_limits* limits, uint32_t width, uint32_t height); #endif // LIBHEIF_SECURITY_LIMITS_H diff --git a/scripts/check-licenses.sh b/scripts/check-licenses.sh index 70227e125a..cd39ecda27 100755 --- a/scripts/check-licenses.sh +++ b/scripts/check-licenses.sh @@ -21,7 +21,7 @@ set -eu # echo "Checking licenses..." -CHECK_RESULT=`/usr/bin/licensecheck --recursive --ignore 'emscripten|libde265|README\.md|post\.js|/.git/|clusterfuzz-testcase-.*' .` +CHECK_RESULT=`/usr/bin/licensecheck --recursive --ignore 'emscripten|libde265|README\.md|post\.js|/.git/|clusterfuzz-testcase-.*|fuzzing/data/.*' .` FOUND= while read -r line; do diff --git a/scripts/install-ci-linux.sh b/scripts/install-ci-linux.sh index 4eb65ce2e6..c2f6509683 100755 --- a/scripts/install-ci-linux.sh +++ b/scripts/install-ci-linux.sh @@ -107,6 +107,14 @@ if [ ! -z "$WITH_GRAPHICS" ]; then libgdk-pixbuf2.0-dev \ libjpeg-dev \ libpng-dev \ + libtiff-dev \ + " +fi + +if [ ! -z "$WITH_UNCOMPRESSED_CODEC" ]; then + INSTALL_PACKAGES="$INSTALL_PACKAGES \ + libbrotli-dev \ + zlib-dev \ " fi @@ -191,7 +199,7 @@ if [ "$WITH_DAV1D" = "1" ]; then export PATH="$PATH:$HOME/.local/bin" cd third-party - sh dav1d.cmd # dav1d does not support this option anymore: -Denable_avx512=false + sh -e dav1d.cmd # dav1d does not support this option anymore: -Denable_avx512=false cd .. fi @@ -200,6 +208,6 @@ if [ "$WITH_RAV1E" = "1" ]; then export PATH="$PATH:$HOME/.cargo/bin" cd third-party - sh rav1e.cmd + sh -e rav1e.cmd cd .. fi diff --git a/scripts/run-ci.sh b/scripts/run-ci.sh index b2a5b267e5..b69050cdee 100755 --- a/scripts/run-ci.sh +++ b/scripts/run-ci.sh @@ -145,6 +145,9 @@ echo "install prefix: ${BUILD_ROOT}/dist" mkdir ${BUILD_ROOT}/dist CMAKE_OPTIONS="$CMAKE_OPTIONS -DCMAKE_INSTALL_PREFIX=${BUILD_ROOT}/dist" +# turn on warnings-as-errors +CMAKE_OPTIONS="$CMAKE_OPTIONS -DCMAKE_COMPILE_WARNING_AS_ERROR=1" + if [ ! -z "$FUZZER" ] && [ "$CURRENT_OS" = "linux" ]; then export ASAN_SYMBOLIZER="$BUILD_ROOT/clang/bin/llvm-symbolizer" diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 82152065b9..a2f17668c1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -4,6 +4,7 @@ include_directories(${libheif_BINARY_DIR} ${libheif_SOURCE_DIR}/libheif ${libhei # prepare C++ configuration file (test-config.cc) set(TESTING_DATA_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/data") +set(LIBHEIFIO_TESTING_DATA_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/data/heifio") configure_file(test-config.cc.in ${CMAKE_BINARY_DIR}/generated/test-config.cc) macro(add_libheif_test TEST_FILE) @@ -13,16 +14,30 @@ macro(add_libheif_test TEST_FILE) add_test(NAME ${TEST_NAME} COMMAND ./${TEST_NAME}) endmacro() +macro(add_heifio_test TEST_FILE) + set(TEST_NAME ${TEST_FILE}) + add_executable(${TEST_NAME} main.cc catch.hpp ${CMAKE_BINARY_DIR}/generated/test-config.cc test_utils.cc ${TEST_FILE}.cc) + target_link_libraries(${TEST_NAME} PRIVATE heif heifio) + target_include_directories(${TEST_NAME} PRIVATE ${libheif_SOURCE_DIR}) + add_test(NAME ${TEST_NAME} COMMAND ./${TEST_NAME}) +endmacro() + # --- tests that require access to internal symbols if (WITH_REDUCED_VISIBILITY) - message(WARNING "Conversion and box unit tests can only be compiled with full symbol visibility (WITH_REDUCED_VISIBILITY=OFF)") + message(WARNING "Several unit tests have been disabled because they can only be compiled with full symbol visibility (WITH_REDUCED_VISIBILITY=OFF)") else() + add_libheif_test(bitstream_tests) add_libheif_test(box_equals) add_libheif_test(conversion) add_libheif_test(idat) add_libheif_test(jpeg2000) add_libheif_test(avc_box) + add_libheif_test(file_layout) +endif() + +if (WITH_EXPERIMENTAL_FEATURS AND WITH_REDUCED_VISIBILITY) + add_libheif_test(pixel_data_types) endif() if (NOT WITH_REDUCED_VISIBILITY AND WITH_UNCOMPRESSED_CODEC) @@ -32,15 +47,16 @@ endif() # --- tests that only access the public API add_libheif_test(encode) +add_libheif_test(extended_type) add_libheif_test(region) -if (WITH_OPENJPH_ENCODER) +if (WITH_OPENJPH_ENCODER AND SUPPORTS_J2K_HT_ENCODING) add_libheif_test(encode_htj2k) else() message(INFO "Disabling HT-JPEG 2000 encoder tests because no HT-JPEG 2000 codec is enabled") endif() -if (WITH_OpenJPEG_ENCODER) +if (WITH_OpenJPEG_ENCODER AND SUPPORTS_J2K_ENCODING) add_libheif_test(encode_jpeg2000) else() message(INFO "Disabling JPEG 2000 encoder tests because no JPEG 2000 codec is enabled") @@ -48,6 +64,7 @@ endif() if (WITH_UNCOMPRESSED_CODEC) add_libheif_test(uncompressed_decode) + add_libheif_test(uncompressed_decode_generic_compression) add_libheif_test(uncompressed_decode_mono) add_libheif_test(uncompressed_decode_rgb) add_libheif_test(uncompressed_decode_rgb16) @@ -60,3 +77,5 @@ if (WITH_UNCOMPRESSED_CODEC) else() message(WARNING "Tests of the 'uncompressed codec' are not compiled because the uncompressed codec is not enabled (WITH_UNCOMPRESSED_CODEC==OFF)") endif () + +add_heifio_test(tiffdecode) diff --git a/tests/avc_box.cc b/tests/avc_box.cc index 4a2fd7bc3b..2866d3d5ad 100644 --- a/tests/avc_box.cc +++ b/tests/avc_box.cc @@ -25,7 +25,7 @@ */ #include "catch.hpp" -#include "codecs/avc.h" +#include "codecs/avc_boxes.h" #include "error.h" #include #include @@ -44,7 +44,7 @@ TEST_CASE("avcC") { BitstreamRange range(reader, byteArray.size()); std::shared_ptr box; - Error error = Box::read(range, &box); + Error error = Box::read(range, &box, heif_get_global_security_limits()); REQUIRE(error == Error::Ok); REQUIRE(range.error() == 0); @@ -68,6 +68,9 @@ TEST_CASE("avcC") { "AVCProfileIndication: 66 (Constrained Baseline)\n" "profile_compatibility: 128\n" "AVCLevelIndication: 30\n" + "Chroma format: 4:2:0\n" + "Bit depth luma: 8\n" + "Bit depth chroma: 8\n" "SPS: 67 64 00 28 ac 72 04 40 40 04 1a 10 00 00 03 00 " "10 00 00 03 03 20 f1 83 18 46 \n" "PPS: 68 e8 43 83 92 c8 b0 \n"); diff --git a/tests/bitstream_tests.cc b/tests/bitstream_tests.cc new file mode 100644 index 0000000000..e30f15937e --- /dev/null +++ b/tests/bitstream_tests.cc @@ -0,0 +1,86 @@ +/* + libheif bitstream unit tests + + 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 "catch.hpp" +#include "error.h" +#include +#include +#include +#include + + +TEST_CASE("read bits") { + std::vector byteArray{0x7f, 0xf1, 0b01000001, 0b10000111, 0b10001111}; + BitReader uut(byteArray.data(), (int)byteArray.size()); + uint32_t byte0 = uut.get_bits(8); + REQUIRE(byte0 == 0x7f); + uint32_t byte1_high = uut.get_bits(4); + REQUIRE(byte1_high == 0x0f); + uint32_t byte1_low = uut.get_bits(4); + REQUIRE(byte1_low == 0x01); + uint32_t byte2_partial1 = uut.get_bits(3); + REQUIRE(byte2_partial1 == 0x02); + uint32_t byte2_partial2 = uut.get_bits(3); + REQUIRE(byte2_partial2 == 0x00); + uint32_t byte2_3_overlap = uut.get_bits(11); + REQUIRE(byte2_3_overlap == 0b0000001100001111); +} + +TEST_CASE("read uint8") { + std::vector byteArray{0x7f, 0xf1, 0b01000001, 0b10000111, 0b10001111}; + BitReader uut(byteArray.data(), (int)byteArray.size()); + uint8_t byte0 = uut.get_bits8(8); + REQUIRE(byte0 == 0x7f); + uint8_t byte1_high = uut.get_bits8(4); + REQUIRE(byte1_high == 0x0f); + uint8_t byte1_low = uut.get_bits8(4); + REQUIRE(byte1_low == 0x01); + uint8_t byte2_partial1 = uut.get_bits8(3); + REQUIRE(byte2_partial1 == 0x02); + uint8_t byte2_partial2 = uut.get_bits8(3); + REQUIRE(byte2_partial2 == 0x00); + uint8_t byte2_3_overlap = uut.get_bits8(8); + REQUIRE((int)byte2_3_overlap == 0b1100001); +} + +TEST_CASE("read uint32") { + std::vector byteArray{0x7f, 0b11110001, 0b01000001, 0b10000111, 0b10001111}; + BitReader uut(byteArray.data(), (int)byteArray.size()); + uint32_t byte0 = uut.get_bits32(8); + REQUIRE(byte0 == 0x7f); + uint32_t byte1_high = uut.get_bits(1); + REQUIRE(byte1_high == 0x01); + uint32_t overlap = uut.get_bits32(30); + REQUIRE(overlap == 0b111000101000001100001111000111); +} + +TEST_CASE("read float") { + std::vector byteArray{0x40, 0x00, 0x00, 0x00}; + std::shared_ptr stream = std::make_shared(byteArray.data(), (int)byteArray.size(), false); + BitstreamRange uut(stream, byteArray.size(), nullptr); + float f = uut.readFloat32(); + REQUIRE(f == 2.0); +} diff --git a/tests/conversion.cc b/tests/conversion.cc index 3ca71419bd..905bd04c34 100644 --- a/tests/conversion.cc +++ b/tests/conversion.cc @@ -70,13 +70,13 @@ std::string PrintChannel(const HeifPixelImage& image, heif_channel channel) { heif_chroma chroma = image.get_chroma_format(); int num_interleaved = num_interleaved_pixels_per_plane(chroma); bool is_interleaved = num_interleaved > 1; - int max_cols = is_interleaved ? 3 : 10; - int max_rows = 10; - int width = std::min(image.get_width(channel), max_cols); - int height = std::min(image.get_height(channel), max_rows); - int stride; + uint32_t max_cols = is_interleaved ? 3 : 10; + uint32_t max_rows = 10; + uint32_t width = std::min(image.get_width(channel), max_cols); + uint32_t height = std::min(image.get_height(channel), max_rows); + uint32_t stride; const T* p = (T*)image.get_plane(channel, &stride); - stride /= sizeof(T); + stride /= (int)sizeof(T); int bpp = image.get_bits_per_pixel(channel); int digits = (int)std::ceil(std::log10(1 << bpp)) + 1; @@ -85,13 +85,13 @@ std::string PrintChannel(const HeifPixelImage& image, heif_channel channel) { << " bpp=" << bpp << "\n"; os << std::string(digits, ' '); int header_width = digits * num_interleaved - 1 + (is_interleaved ? 3 : 0); - for (int x = 0; x < width; ++x) { + for (uint32_t x = 0; x < width; ++x) { os << "|" << std::left << std::setw(header_width) << std::to_string(x); } os << "\n"; - for (int y = 0; y < height; ++y) { + for (uint32_t y = 0; y < height; ++y) { os << std::left << std::setw(digits) << std::to_string(y) << "|"; - for (int x = 0; x < width; ++x) { + for (uint32_t x = 0; x < width; ++x) { if (is_interleaved) os << "("; for (int k = 0; k < num_interleaved; ++k) { int v = SwapBytesIfNeeded(p[y * stride + x * num_interleaved + k], chroma); @@ -118,23 +118,23 @@ std::string PrintChannel(const HeifPixelImage& image, heif_channel channel) { template double GetPsnr(const HeifPixelImage& original, const HeifPixelImage& compressed, heif_channel channel, bool expect_alpha_max) { - int w = original.get_width(channel); - int h = original.get_height(channel); + uint32_t w = original.get_width(channel); + uint32_t h = original.get_height(channel); heif_chroma chroma = original.get_chroma_format(); - int orig_stride; - int compressed_stride; + uint32_t orig_stride; + uint32_t compressed_stride; const T* orig_p = (T*)original.get_plane(channel, &orig_stride); const T* compressed_p = (T*)compressed.get_plane(channel, &compressed_stride); - orig_stride /= sizeof(T); - compressed_stride /= sizeof(T); + orig_stride /= (int)sizeof(T); + compressed_stride /= (int)sizeof(T); double mse = 0.0; int num_interleaved = num_interleaved_pixels_per_plane(chroma); int alpha_max = (1 << original.get_bits_per_pixel(channel)) - 1; CAPTURE(expect_alpha_max); - for (int y = 0; y < h; y++) { - for (int x = 0; x < w * num_interleaved; x++) { + for (uint32_t y = 0; y < h; y++) { + for (uint32_t x = 0; x < w * num_interleaved; x++) { int orig_v = SwapBytesIfNeeded(orig_p[y * orig_stride + x], chroma); if (expect_alpha_max && (channel == heif_channel_Alpha || ((num_interleaved == 4) && x % 4 == 3))) { @@ -283,8 +283,8 @@ void TestConversion(const std::string& test_name, const ColorState& input_state, int height = 8; REQUIRE(MakeTestImage(input_state, width, height, in_image.get())); - std::shared_ptr out_image = - pipeline.convert_image(in_image); + std::shared_ptr out_image; + out_image = pipeline.convert_image(in_image); REQUIRE(out_image != nullptr); CHECK(out_image->get_colorspace() == target_state.colorspace); @@ -292,7 +292,7 @@ void TestConversion(const std::string& test_name, const ColorState& input_state, CHECK(out_image->has_alpha() == target_state.has_alpha); for (const Plane& plane : GetPlanes(target_state, width, height)) { INFO("Channel: " << plane.channel); - int stride; + uint32_t stride; CHECK(out_image->get_plane(plane.channel, &stride) != nullptr); CHECK(out_image->get_bits_per_pixel(plane.channel) == target_state.bits_per_pixel); @@ -602,7 +602,7 @@ static void fill_plane(std::shared_ptr& img, heif_channel channe { img->add_plane(channel, w, h, 8); - int stride; + uint32_t stride; uint8_t* p = img->get_plane(channel, &stride); for (int y = 0; y < h; y++) { @@ -616,15 +616,15 @@ static void fill_plane(std::shared_ptr& img, heif_channel channe static void assert_plane(std::shared_ptr& img, heif_channel channel, const std::vector& pixels) { INFO("channel: " << channel); - int w = img->get_width(channel); - int h = img->get_height(channel); + uint32_t w = img->get_width(channel); + uint32_t h = img->get_height(channel); - int stride; + uint32_t stride; uint8_t* p = img->get_plane(channel, &stride); - for (int y = 0; y < h; y++) { + for (uint32_t y = 0; y < h; y++) { INFO("row: " << y); - for (int x = 0; x < w; x++) { + for (uint32_t x = 0; x < w; x++) { INFO("column: " << x); REQUIRE((int)p[y * stride + x] == (int)pixels[y * w + x]); } @@ -674,8 +674,8 @@ TEST_CASE("RGB 5-6-5 to RGB") heif_color_conversion_options options = {}; std::shared_ptr img = std::make_shared(); - const int width = 3; - const int height = 2; + const uint32_t width = 3; + const uint32_t height = 2; img->create(width, height, heif_colorspace_RGB, heif_chroma_444); img->add_plane(heif_channel_R, width, height, 5); REQUIRE(img->get_bits_per_pixel(heif_channel_R) == 5); @@ -686,12 +686,12 @@ TEST_CASE("RGB 5-6-5 to RGB") uint8_t v = 1; for (heif_channel plane: {heif_channel_R, heif_channel_G, heif_channel_B}) { - int dst_stride = 0; + uint32_t dst_stride = 0; uint8_t *dst = img->get_plane(plane, &dst_stride); - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { + for (uint32_t y = 0; y < height; y++) { + for (uint32_t x = 0; x < width; x++) { dst[y * dst_stride + x] = v; - v += 1; + v++; } } } diff --git a/tests/data/heifio/mono.tif b/tests/data/heifio/mono.tif new file mode 100644 index 0000000000..6069f02e60 Binary files /dev/null and b/tests/data/heifio/mono.tif differ diff --git a/tests/data/heifio/mono_planar.tif b/tests/data/heifio/mono_planar.tif new file mode 100644 index 0000000000..bf21da93f1 Binary files /dev/null and b/tests/data/heifio/mono_planar.tif differ diff --git a/tests/data/heifio/rgb.tif b/tests/data/heifio/rgb.tif new file mode 100644 index 0000000000..ca6a0edc29 Binary files /dev/null and b/tests/data/heifio/rgb.tif differ diff --git a/tests/data/heifio/rgb_planar.tif b/tests/data/heifio/rgb_planar.tif new file mode 100644 index 0000000000..0020c20014 Binary files /dev/null and b/tests/data/heifio/rgb_planar.tif differ diff --git a/tests/data/heifio/rgba.tif b/tests/data/heifio/rgba.tif new file mode 100644 index 0000000000..07ab2e82fd Binary files /dev/null and b/tests/data/heifio/rgba.tif differ diff --git a/tests/data/heifio/rgba_planar.tif b/tests/data/heifio/rgba_planar.tif new file mode 100644 index 0000000000..bc6a964d5a Binary files /dev/null and b/tests/data/heifio/rgba_planar.tif differ diff --git a/tests/data/rgb_generic_compressed_brotli.heif b/tests/data/rgb_generic_compressed_brotli.heif new file mode 100644 index 0000000000..8d44e6e2b6 Binary files /dev/null and b/tests/data/rgb_generic_compressed_brotli.heif differ diff --git a/tests/data/rgb_generic_compressed_defl.heif b/tests/data/rgb_generic_compressed_defl.heif new file mode 100644 index 0000000000..580e5848a3 Binary files /dev/null and b/tests/data/rgb_generic_compressed_defl.heif differ diff --git a/tests/data/rgb_generic_compressed_tile_deflate.heif b/tests/data/rgb_generic_compressed_tile_deflate.heif new file mode 100644 index 0000000000..7c2acae025 Binary files /dev/null and b/tests/data/rgb_generic_compressed_tile_deflate.heif differ diff --git a/tests/data/rgb_generic_compressed_zlib.heif b/tests/data/rgb_generic_compressed_zlib.heif new file mode 100644 index 0000000000..d7c4a8be6a Binary files /dev/null and b/tests/data/rgb_generic_compressed_zlib.heif differ diff --git a/tests/data/rgb_generic_compressed_zlib_rows.heif b/tests/data/rgb_generic_compressed_zlib_rows.heif new file mode 100644 index 0000000000..76a7692681 Binary files /dev/null and b/tests/data/rgb_generic_compressed_zlib_rows.heif differ diff --git a/tests/data/rgb_generic_compressed_zlib_tiled.heif b/tests/data/rgb_generic_compressed_zlib_tiled.heif new file mode 100644 index 0000000000..8532c66bfd Binary files /dev/null and b/tests/data/rgb_generic_compressed_zlib_tiled.heif differ diff --git a/tests/extended_type.cc b/tests/extended_type.cc new file mode 100644 index 0000000000..ba8e531d3f --- /dev/null +++ b/tests/extended_type.cc @@ -0,0 +1,93 @@ +/* + libheif integration tests for extended type (uuid) boxes + + 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 "catch.hpp" +#include "libheif/api_structs.h" +#include "libheif/heif.h" +#include "test-config.h" +#include "test_utils.h" +#include +#include +#include + + +TEST_CASE("make extended type") { + heif_image *input_image = createImage_RGB_planar(); + heif_init(nullptr); + heif_context *ctx = heif_context_alloc(); + heif_encoder *encoder; + struct heif_error err; + err = heif_context_get_encoder_for_format(ctx, heif_compression_HEVC, &encoder); + REQUIRE(err.code == heif_error_Ok); + + struct heif_encoding_options *options = heif_encoding_options_alloc(); + + heif_image_handle *output_image_handle; + + err = heif_context_encode_image(ctx, input_image, encoder, nullptr, &output_image_handle); + REQUIRE(err.code == heif_error_Ok); + + heif_item_id itemId; + err = heif_context_get_primary_image_ID(ctx, &itemId); + REQUIRE(err.code == heif_error_Ok); + + const uint8_t uuid[] = {0x13, 0x7a, 0x17, 0x42, 0x75, 0xac, 0x47, 0x47, 0x82, 0xbc, 0x65, 0x95, 0x76, 0xe8, 0x67, 0x5b}; + std::vector body {0x00, 0x00, 0x00, 0x01, 0xfa, 0xde, 0x99, 0x04}; + heif_property_id propertyId; + err = heif_item_add_raw_property(ctx, itemId, heif_item_property_type_uuid, &uuid[0], body.data(), body.size(), 0, &propertyId); + REQUIRE(err.code == heif_error_Ok); + REQUIRE(propertyId == 4); + err = heif_context_write_to_file(ctx, "with_uuid.heif"); + REQUIRE(err.code == heif_error_Ok); + + uint8_t extended_type[16]; + err = heif_item_get_property_uuid_type(ctx, itemId, propertyId, &extended_type[0]); + REQUIRE(err.code == heif_error_Ok); + for (int i = 0; i < 16; i++) { + REQUIRE(extended_type[i] == uuid[i]); + } + + size_t size = 0; + err = heif_item_get_property_raw_size(ctx, itemId, propertyId, &size); + REQUIRE(err.code == heif_error_Ok); + REQUIRE(size == 8); + + uint8_t data[8]; + err = heif_item_get_property_raw_data(ctx, itemId, propertyId, &data[0]); + REQUIRE(err.code == heif_error_Ok); + for (int i = 0; i < 8; i++) { + REQUIRE(data[i] == body[i]); + } + + heif_image_handle_release(output_image_handle); + heif_encoding_options_free(options); + heif_encoder_release(encoder); + heif_image_release(input_image); + + heif_context_free(ctx); + heif_deinit(); +} + diff --git a/tests/file_layout.cc b/tests/file_layout.cc new file mode 100644 index 0000000000..084406ee8b --- /dev/null +++ b/tests/file_layout.cc @@ -0,0 +1,49 @@ +/* + libheif integration tests for uncompressed decoder + + 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 "catch.hpp" +#include "libheif/heif.h" +#include "libheif/api_structs.h" +#include +#include +#include "test_utils.h" +#include +#include "file_layout.h" +#include "test-config.h" + +#include + + +TEST_CASE("parse file layout") { + auto istr = std::unique_ptr(new std::ifstream(tests_data_directory + "/uncompressed_comp_ABGR.heif", std::ios::binary)); + auto reader = std::make_shared(std::move(istr)); + + FileLayout file; + Error err = file.read(reader, heif_get_global_security_limits()); + + REQUIRE(err.error_code == heif_error_Ok); + + // TODO: read file where 'meta' box is not the first one after 'ftyp' +} diff --git a/tests/idat.cc b/tests/idat.cc index 7782516891..ca43db05b6 100644 --- a/tests/idat.cc +++ b/tests/idat.cc @@ -38,7 +38,7 @@ TEST_CASE("idat bad") { BitstreamRange range(reader, testData.size()); for (;;) { std::shared_ptr box; - Error error = Box::read(range, &box); + Error error = Box::read(range, &box, heif_get_global_security_limits()); if (error != Error::Ok || range.error()) { break; } diff --git a/tests/jpeg2000.cc b/tests/jpeg2000.cc index f528a5ddcc..4b97fb57f1 100644 --- a/tests/jpeg2000.cc +++ b/tests/jpeg2000.cc @@ -26,7 +26,7 @@ #include "catch.hpp" #include "libheif/heif.h" -#include "codecs/jpeg2000.h" +#include "codecs/jpeg2000_boxes.h" #include #include diff --git a/tests/pixel_data_types.cc b/tests/pixel_data_types.cc new file mode 100644 index 0000000000..505e46ed29 --- /dev/null +++ b/tests/pixel_data_types.cc @@ -0,0 +1,176 @@ +/* + libheif unit tests + + 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 "pixelimage.h" +#include "catch.hpp" + +TEST_CASE( "uint32_t" ) +{ + HeifPixelImage image; + + image.create(3,2, heif_colorspace_nonvisual, heif_chroma_undefined); + image.add_channel(heif_channel_Y, 3,2, heif_channel_datatype_unsigned_integer, 32); + + uint32_t stride; + uint32_t* data = image.get_channel(heif_channel_Y, &stride); + + REQUIRE(stride >= 3); + REQUIRE(image.get_width(heif_channel_Y) == 3); + REQUIRE(image.get_height(heif_channel_Y) == 2); + REQUIRE(image.get_bits_per_pixel(heif_channel_Y) == 32); + REQUIRE(image.get_storage_bits_per_pixel(heif_channel_Y) == 32); + REQUIRE(image.get_datatype(heif_channel_Y) == heif_channel_datatype_unsigned_integer); + REQUIRE(image.get_number_of_interleaved_components(heif_channel_Y) == 1); + + data[0*stride + 0] = 0; + data[0*stride + 1] = 0xFFFFFFFFu; + data[0*stride + 2] = 1000; + data[1*stride + 0] = 0xFFFFFFFFu; + data[1*stride + 1] = 0; + data[1*stride + 2] = 2000; + + REQUIRE(data[0*stride + 1] == 0xFFFFFFFFu); + + // --- rotate data + + std::shared_ptr rot; + auto rotationResult = image.rotate_ccw(90); + REQUIRE(rotationResult.error.error_code == heif_error_Ok); + rot = rotationResult.value; + + data = rot->get_channel(heif_channel_Y, &stride); + + REQUIRE(data[0*stride + 0] == 1000); + REQUIRE(data[0*stride + 1] == 2000); + REQUIRE(data[1*stride + 0] == 0xFFFFFFFFu); + REQUIRE(data[1*stride + 1] == 0); + REQUIRE(data[2*stride + 0] == 0); + REQUIRE(data[2*stride + 1] == 0xFFFFFFFFu); + + // --- mirror + + rot->mirror_inplace(heif_transform_mirror_direction_horizontal); + + REQUIRE(data[0*stride + 1] == 1000); + REQUIRE(data[0*stride + 0] == 2000); + REQUIRE(data[1*stride + 1] == 0xFFFFFFFFu); + REQUIRE(data[1*stride + 0] == 0); + REQUIRE(data[2*stride + 1] == 0); + REQUIRE(data[2*stride + 0] == 0xFFFFFFFFu); + + rot->mirror_inplace(heif_transform_mirror_direction_vertical); + + REQUIRE(data[2*stride + 1] == 1000); + REQUIRE(data[2*stride + 0] == 2000); + REQUIRE(data[1*stride + 1] == 0xFFFFFFFFu); + REQUIRE(data[1*stride + 0] == 0); + REQUIRE(data[0*stride + 1] == 0); + REQUIRE(data[0*stride + 0] == 0xFFFFFFFFu); + + // --- crop + + std::shared_ptr crop; + auto cropResult = image.crop(1,2,1,1); + REQUIRE(cropResult.error.error_code == heif_error_Ok); + crop = cropResult.value; + + REQUIRE(crop->get_width(heif_channel_Y) == 2); + REQUIRE(crop->get_height(heif_channel_Y) == 1); + + data = crop->get_channel(heif_channel_Y, &stride); + + REQUIRE(data[0*stride + 0] == 0); + REQUIRE(data[0*stride + 1] == 2000); + + cropResult = image.crop(0,1,0,1); + REQUIRE(cropResult.error.error_code == heif_error_Ok); + crop = cropResult.value; + + REQUIRE(crop->get_width(heif_channel_Y) == 2); + REQUIRE(crop->get_height(heif_channel_Y) == 2); + + data = crop->get_channel(heif_channel_Y, &stride); + + REQUIRE(data[0*stride + 0] == 0); + REQUIRE(data[0*stride + 1] == 0xFFFFFFFFu); + REQUIRE(data[1*stride + 0] == 0xFFFFFFFFu); + REQUIRE(data[1*stride + 1] == 0); +} + + +TEST_CASE( "complex64_t" ) +{ + HeifPixelImage image; + + image.create(3,2, heif_colorspace_nonvisual, heif_chroma_undefined); + image.add_channel(heif_channel_Y, 3,2, heif_channel_datatype_complex_number, 128); + + uint32_t stride; + heif_complex64* data = image.get_channel(heif_channel_Y, &stride); + + REQUIRE(stride >= 3); + REQUIRE(image.get_width(heif_channel_Y) == 3); + REQUIRE(image.get_height(heif_channel_Y) == 2); + REQUIRE(image.get_bits_per_pixel(heif_channel_Y) == 128); + REQUIRE(image.get_storage_bits_per_pixel(heif_channel_Y) == 128); + REQUIRE(image.get_datatype(heif_channel_Y) == heif_channel_datatype_complex_number); + REQUIRE(image.get_number_of_interleaved_components(heif_channel_Y) == 1); + + data[0*stride + 0] = {0.0, -1.0}; + data[0*stride + 1] = {1.0, 2.0}; + data[0*stride + 2] = {2.0, -1.0}; + data[1*stride + 0] = {0.25, 0.5}; + data[1*stride + 1] = {0.0, 0.0}; + data[1*stride + 2] = {-0.75, 0.125}; + + REQUIRE(data[0*stride + 1].real == 1.0); + REQUIRE(data[0*stride + 1].imaginary == 2.0); +} + + +TEST_CASE( "image datatype through public API" ) +{ + heif_image* image; + heif_error error = heif_image_create(3,2,heif_colorspace_nonvisual, heif_chroma_undefined, &image); + REQUIRE(!error.code); + + heif_image_add_channel(image, heif_channel_Y, 3,2, heif_channel_datatype_unsigned_integer, 32); + + uint32_t stride; + uint32_t* data = heif_image_get_channel_uint32(image, heif_channel_Y, &stride); + REQUIRE(data != nullptr); + + REQUIRE(stride >= 3); + REQUIRE(heif_image_get_datatype(image, heif_channel_Y) == heif_channel_datatype_unsigned_integer); + REQUIRE(heif_image_get_bits_per_pixel_range(image, heif_channel_Y) == 32); + + data[stride*0 + 0] = 0xFFFFFFFFu; + data[stride*0 + 1] = 0; + data[stride*0 + 2] = 1000; + data[stride*1 + 0] = 0xFFFFFFFFu; + data[stride*1 + 1] = 200; + data[stride*1 + 2] = 5; +} diff --git a/tests/test-config.cc.in b/tests/test-config.cc.in index d6cb7fa30f..cb356a6296 100644 --- a/tests/test-config.cc.in +++ b/tests/test-config.cc.in @@ -27,3 +27,4 @@ #include std::string tests_data_directory = "@TESTING_DATA_DIRECTORY@"; +std::string libheifio_tests_data_directory = "@LIBHEIFIO_TESTING_DATA_DIRECTORY@"; diff --git a/tests/test-config.h b/tests/test-config.h index c1559bf140..6664982f63 100644 --- a/tests/test-config.h +++ b/tests/test-config.h @@ -31,5 +31,6 @@ // Absolute path to the tests/data directory. Can be used by the tests to load input data. extern std::string tests_data_directory; +extern std::string libheifio_tests_data_directory; #endif //LIBHEIF_TEST_CONFIG_H diff --git a/tests/test_utils.cc b/tests/test_utils.cc index 08ac28e286..fbfae7e9d6 100644 --- a/tests/test_utils.cc +++ b/tests/test_utils.cc @@ -168,3 +168,9 @@ struct heif_image * createImage_RGB_planar() return image; } + + +std::string get_path_for_heifio_test_file(std::string filename) +{ + return libheifio_tests_data_directory + "/" + filename; +} diff --git a/tests/test_utils.h b/tests/test_utils.h index 35353255ce..823e8f9477 100644 --- a/tests/test_utils.h +++ b/tests/test_utils.h @@ -38,4 +38,6 @@ struct heif_image * get_primary_image_ycbcr(heif_image_handle * handle, heif_chr void fill_new_plane(heif_image* img, heif_channel channel, int w, int h); -struct heif_image * createImage_RGB_planar(); \ No newline at end of file +struct heif_image * createImage_RGB_planar(); + +std::string get_path_for_heifio_test_file(std::string filename); diff --git a/tests/tiffdecode.cc b/tests/tiffdecode.cc new file mode 100644 index 0000000000..0c4265c8c2 --- /dev/null +++ b/tests/tiffdecode.cc @@ -0,0 +1,143 @@ +/* + libheifio TIFF decode unit tests + + 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 "catch.hpp" +#include +#include +#include "heifio/decoder.h" +#include "heifio/decoder_tiff.h" +#include "test_utils.h" +#include "libheif/heif.h" +#include "libheif/api_structs.h" + +#if HAVE_LIBTIFF +void checkMono(InputImage input_image) { + REQUIRE(input_image.orientation == heif_orientation_normal); + REQUIRE(input_image.image != nullptr); + const struct heif_image* image = input_image.image.get(); + REQUIRE(heif_image_get_colorspace(image) == heif_colorspace_monochrome); + REQUIRE(heif_image_get_chroma_format(image) == heif_chroma_monochrome); + REQUIRE(heif_image_get_width(image, heif_channel_Y) == 128); + REQUIRE(heif_image_get_height(image, heif_channel_Y) == 64); + REQUIRE(heif_image_get_bits_per_pixel(image, heif_channel_Y) == 8); + REQUIRE(heif_image_get_bits_per_pixel_range(image, heif_channel_Y) == 8); + REQUIRE(heif_image_has_channel(image, heif_channel_Y) == 1); + REQUIRE(heif_image_has_channel(image, heif_channel_R) == 0); + REQUIRE(heif_image_has_channel(image, heif_channel_G) == 0); + REQUIRE(heif_image_has_channel(image, heif_channel_B) == 0); + REQUIRE(heif_image_has_channel(image, heif_channel_interleaved) == 0); + REQUIRE(heif_image_has_channel(image, heif_channel_Cr) == 0); + REQUIRE(heif_image_has_channel(image, heif_channel_Cb) == 0); +} + +TEST_CASE("mono8") { + InputImage input_image; + std::string path = get_path_for_heifio_test_file("mono.tif"); + heif_error err = loadTIFF(path.c_str(), &input_image); + REQUIRE(err.code == heif_error_Ok); + checkMono(input_image); +} + +TEST_CASE("mono8planar") { + InputImage input_image; + std::string path = get_path_for_heifio_test_file("mono_planar.tif"); + heif_error err = loadTIFF(path.c_str(), &input_image); + REQUIRE(err.code == heif_error_Ok); + checkMono(input_image); +} + +void checkRGB(InputImage input_image) { + REQUIRE(input_image.orientation == heif_orientation_normal); + REQUIRE(input_image.image != nullptr); + const struct heif_image* image = input_image.image.get(); + REQUIRE(heif_image_get_colorspace(image) == heif_colorspace_RGB); + REQUIRE(heif_image_get_chroma_format(image) == heif_chroma_interleaved_RGB); + REQUIRE(heif_image_get_width(image, heif_channel_interleaved) == 32); + REQUIRE(heif_image_get_height(image, heif_channel_interleaved) == 10); + REQUIRE(heif_image_get_bits_per_pixel(image, heif_channel_interleaved) == 24); + REQUIRE(heif_image_get_bits_per_pixel_range(image, heif_channel_interleaved) == 8); + REQUIRE(heif_image_has_channel(image, heif_channel_Y) == 0); + REQUIRE(heif_image_has_channel(image, heif_channel_R) == 0); + REQUIRE(heif_image_has_channel(image, heif_channel_G) == 0); + REQUIRE(heif_image_has_channel(image, heif_channel_B) == 0); + REQUIRE(heif_image_has_channel(image, heif_channel_interleaved) == 1); + REQUIRE(heif_image_has_channel(image, heif_channel_Cr) == 0); + REQUIRE(heif_image_has_channel(image, heif_channel_Cb) == 0); +} + +TEST_CASE("rgb") { + InputImage input_image; + std::string path = get_path_for_heifio_test_file("rgb.tif"); + heif_error err = loadTIFF(path.c_str(), &input_image); + REQUIRE(err.code == heif_error_Ok); + checkRGB(input_image); +} + +TEST_CASE("rgb_planar") { + InputImage input_image; + std::string path = get_path_for_heifio_test_file("rgb_planar.tif"); + heif_error err = loadTIFF(path.c_str(), &input_image); + REQUIRE(err.code == heif_error_Ok); + checkRGB(input_image); +} + + +void checkRGBA(InputImage input_image) { + REQUIRE(input_image.orientation == heif_orientation_normal); + REQUIRE(input_image.image != nullptr); + const struct heif_image* image = input_image.image.get(); + REQUIRE(heif_image_get_colorspace(image) == heif_colorspace_RGB); + REQUIRE(heif_image_get_chroma_format(image) == heif_chroma_interleaved_RGBA); + REQUIRE(heif_image_get_width(image, heif_channel_interleaved) == 32); + REQUIRE(heif_image_get_height(image, heif_channel_interleaved) == 10); + REQUIRE(heif_image_get_bits_per_pixel(image, heif_channel_interleaved) == 32); + REQUIRE(heif_image_get_bits_per_pixel_range(image, heif_channel_interleaved) == 8); + REQUIRE(heif_image_has_channel(image, heif_channel_Y) == 0); + REQUIRE(heif_image_has_channel(image, heif_channel_R) == 0); + REQUIRE(heif_image_has_channel(image, heif_channel_G) == 0); + REQUIRE(heif_image_has_channel(image, heif_channel_B) == 0); + REQUIRE(heif_image_has_channel(image, heif_channel_interleaved) == 1); + REQUIRE(heif_image_has_channel(image, heif_channel_Cr) == 0); + REQUIRE(heif_image_has_channel(image, heif_channel_Cb) == 0); +} + +TEST_CASE("rgba") { + InputImage input_image; + std::string path = get_path_for_heifio_test_file("rgba.tif"); + heif_error err = loadTIFF(path.c_str(), &input_image); + REQUIRE(err.code == heif_error_Ok); + checkRGBA(input_image); +} + +TEST_CASE("rgba_planar") { + InputImage input_image; + std::string path = get_path_for_heifio_test_file("rgba_planar.tif"); + heif_error err = loadTIFF(path.c_str(), &input_image); + REQUIRE(err.code == heif_error_Ok); + checkRGBA(input_image); +} + +#endif // HAVE_LIBTIFF \ No newline at end of file diff --git a/tests/uncompressed_box.cc b/tests/uncompressed_box.cc index 630e79b1f3..6b0506010d 100644 --- a/tests/uncompressed_box.cc +++ b/tests/uncompressed_box.cc @@ -27,8 +27,8 @@ #include "catch.hpp" #include "box.h" #include "libheif/heif.h" -#include "codecs/uncompressed.h" -#include "codecs/uncompressed_box.h" +#include "codecs/uncompressed/unc_types.h" +#include "codecs/uncompressed/unc_boxes.h" #include #include @@ -214,5 +214,435 @@ TEST_CASE( "uncC" ) Indent indent; std::string dump_output = uncC->dump(indent); - REQUIRE(dump_output == "Box: uncC -----\nsize: 0 (header size: 0)\nprofile: 1919378017 (rgba)\ncomponent_index: 0\ncomponent_bit_depth: 8\ncomponent_format: unsigned\ncomponent_align_size: 0\ncomponent_index: 1\ncomponent_bit_depth: 8\ncomponent_format: unsigned\ncomponent_align_size: 0\ncomponent_index: 2\ncomponent_bit_depth: 8\ncomponent_format: unsigned\ncomponent_align_size: 0\ncomponent_index: 3\ncomponent_bit_depth: 8\ncomponent_format: unsigned\ncomponent_align_size: 0\nsampling_type: no subsampling\ninterleave_type: pixel\nblock_size: 0\ncomponents_little_endian: 0\nblock_pad_lsb: 0\nblock_little_endian: 0\nblock_reversed: 0\npad_unknown: 0\npixel_size: 0\nrow_align_size: 0\ntile_align_size: 0\nnum_tile_cols: 1\nnum_tile_rows: 1\n"); + REQUIRE(dump_output == "Box: uncC -----\nsize: 0 (header size: 0)\nprofile: 1919378017 (rgba)\ncomponent_index: 0\n| component_bit_depth: 8\n| component_format: unsigned\n| component_align_size: 0\ncomponent_index: 1\n| component_bit_depth: 8\n| component_format: unsigned\n| component_align_size: 0\ncomponent_index: 2\n| component_bit_depth: 8\n| component_format: unsigned\n| component_align_size: 0\ncomponent_index: 3\n| component_bit_depth: 8\n| component_format: unsigned\n| component_align_size: 0\nsampling_type: no subsampling\ninterleave_type: pixel\nblock_size: 0\ncomponents_little_endian: 0\nblock_pad_lsb: 0\nblock_little_endian: 0\nblock_reversed: 0\npad_unknown: 0\npixel_size: 0\nrow_align_size: 0\ntile_align_size: 0\nnum_tile_cols: 1\nnum_tile_rows: 1\n"); +} + +TEST_CASE("uncC_parse") { + std::vector byteArray{ + 0x00, 0x00, 0x00, 0x40, 'u', 'n', 'c', 'C', + 0x00, 0x00, 0x00, 0x00, 'r', 'g', 'b', 'a', + 0x00, 0x00, 0x00, 0x04, 0, 0, 7, 0x00, + 0x00, 0x00, 0x01, 0x07, 0x00, 0x00, 0x00, 0x02, + 0x07, 0x00, 0x00, 0x00, 0x03, 0x07, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02 + }; + + auto reader = std::make_shared(byteArray.data(), + byteArray.size(), false); + + BitstreamRange range(reader, byteArray.size()); + std::shared_ptr box; + Error error = Box::read(range, &box, heif_get_global_security_limits()); + REQUIRE(error == Error::Ok); + REQUIRE(range.error() == 0); + + REQUIRE(box->get_short_type() == fourcc("uncC")); + REQUIRE(box->get_type_string() == "uncC"); + std::shared_ptr uncC = std::dynamic_pointer_cast(box); + REQUIRE(uncC->get_number_of_tile_columns() == 2); + REQUIRE(uncC->get_number_of_tile_rows() == 3); + Indent indent; + std::string dumpResult = box->dump(indent); + REQUIRE(dumpResult == "Box: uncC -----\n" + "size: 64 (header size: 12)\n" + "profile: 1919378017 (rgba)\n" + "component_index: 0\n" + "| component_bit_depth: 8\n" + "| component_format: unsigned\n" + "| component_align_size: 0\n" + "component_index: 1\n" + "| component_bit_depth: 8\n" + "| component_format: unsigned\n" + "| component_align_size: 0\n" + "component_index: 2\n" + "| component_bit_depth: 8\n" + "| component_format: unsigned\n" + "| component_align_size: 0\n" + "component_index: 3\n" + "| component_bit_depth: 8\n" + "| component_format: unsigned\n" + "| component_align_size: 0\n" + "sampling_type: no subsampling\n" + "interleave_type: pixel\n" + "block_size: 0\n" + "components_little_endian: 0\n" + "block_pad_lsb: 0\n" + "block_little_endian: 0\n" + "block_reversed: 0\n" + "pad_unknown: 0\n" + "pixel_size: 0\n" + "row_align_size: 0\n" + "tile_align_size: 0\n" + "num_tile_cols: 2\n" + "num_tile_rows: 3\n"); +} + +TEST_CASE("uncC_parse_no_overflow") { + std::vector byteArray{ + 0x00, 0x00, 0x00, 0x40, 'u', 'n', 'c', 'C', + 0x00, 0x00, 0x00, 0x00, 'r', 'g', 'b', 'a', + 0x00, 0x00, 0x00, 0x04, 0, 0, 7, 0x00, + 0x00, 0x00, 0x01, 0x07, 0x00, 0x00, 0x00, 0x02, + 0x07, 0x00, 0x00, 0x00, 0x03, 0x07, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xfe, 0xff, 0xff, 0xff, 0xfe + }; + + auto reader = std::make_shared(byteArray.data(), + byteArray.size(), false); + + BitstreamRange range(reader, byteArray.size()); + std::shared_ptr box; + Error error = Box::read(range, &box, heif_get_global_security_limits()); + REQUIRE(error == Error::Ok); + REQUIRE(range.error() == 0); + + REQUIRE(box->get_short_type() == fourcc("uncC")); + REQUIRE(box->get_type_string() == "uncC"); + std::shared_ptr uncC = std::dynamic_pointer_cast(box); + REQUIRE(uncC->get_number_of_tile_columns() == 4294967295); + REQUIRE(uncC->get_number_of_tile_rows() == 4294967295); +} + +TEST_CASE("uncC_parse_excess_tile_cols") { + std::vector byteArray{ + 0x00, 0x00, 0x00, 0x40, 'u', 'n', 'c', 'C', + 0x00, 0x00, 0x00, 0x00, 'r', 'g', 'b', 'a', + 0x00, 0x00, 0x00, 0x04, 0, 0, 7, 0x00, + 0x00, 0x00, 0x01, 0x07, 0x00, 0x00, 0x00, 0x02, + 0x07, 0x00, 0x00, 0x00, 0x03, 0x07, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xff, 0xff, 0xff, 0xff, 0x00, 0x00, 0x7f, 0xff + }; + + auto reader = std::make_shared(byteArray.data(), + byteArray.size(), false); + BitstreamRange range(reader, byteArray.size()); + std::shared_ptr box; + Error error = Box::read(range, &box, heif_get_global_security_limits()); + REQUIRE(range.error() == 0); + REQUIRE(error.error_code == 6); + REQUIRE(error.sub_error_code == 1000); + REQUIRE(error.message == "Tiling size 4294967296 x 32768 exceeds the maximum allowed size 4294967295 x 4294967295"); +} + +TEST_CASE("uncC_parse_excess_tile_rows") { + std::vector byteArray{ + 0x00, 0x00, 0x00, 0x40, 'u', 'n', 'c', 'C', + 0x00, 0x00, 0x00, 0x00, 'r', 'g', 'b', 'a', + 0x00, 0x00, 0x00, 0x04, 0, 0, 7, 0x00, + 0x00, 0x00, 0x01, 0x07, 0x00, 0x00, 0x00, 0x02, + 0x07, 0x00, 0x00, 0x00, 0x03, 0x07, 0x00, 0x00, + 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x7f, 0xff, 0xff, 0xff, 0xff, 0xff + }; + + auto reader = std::make_shared(byteArray.data(), + byteArray.size(), false); + BitstreamRange range(reader, byteArray.size()); + std::shared_ptr box; + Error error = Box::read(range, &box, heif_get_global_security_limits()); + REQUIRE(range.error() == 0); + REQUIRE(error.error_code == 6); + REQUIRE(error.sub_error_code == 1000); + REQUIRE(error.message == "Tiling size 32768 x 4294967296 exceeds the maximum allowed size 4294967295 x 4294967295"); +} + +TEST_CASE("cmpC_defl") { + std::vector byteArray{ + 0x00, 0x00, 0x00, 0x11, 'c', 'm', 'p', 'C', + 0x00, 0x00, 0x00, 0x00, 'd', 'e', 'f', 'l', + 0x00 + }; + + auto reader = std::make_shared(byteArray.data(), + byteArray.size(), false); + + BitstreamRange range(reader, byteArray.size()); + std::shared_ptr box; + Error error = Box::read(range, &box, heif_get_global_security_limits()); + REQUIRE(error == Error::Ok); + REQUIRE(range.error() == 0); + + REQUIRE(box->get_short_type() == fourcc("cmpC")); + REQUIRE(box->get_type_string() == "cmpC"); + std::shared_ptr cmpC = std::dynamic_pointer_cast(box); + REQUIRE(cmpC != nullptr); + REQUIRE(cmpC->get_compression_type() == fourcc("defl")); + REQUIRE(cmpC->get_compressed_unit_type() == 0); + + StreamWriter writer; + Error err = cmpC->write(writer); + REQUIRE(err.error_code == heif_error_Ok); + const std::vector written = writer.get_data(); + REQUIRE(written == byteArray); + + Indent indent; + std::string dump_output = cmpC->dump(indent); + REQUIRE(dump_output == "Box: cmpC -----\nsize: 17 (header size: 12)\ncompression_type: defl\ncompressed_entity_type: 0\n"); + +} + + +TEST_CASE("cmpC_zlib") { + std::vector byteArray{ + 0x00, 0x00, 0x00, 0x11, 'c', 'm', 'p', 'C', + 0x00, 0x00, 0x00, 0x00, 'z', 'l', 'i', 'b', + 0x02 + }; + + auto reader = std::make_shared(byteArray.data(), + byteArray.size(), false); + + BitstreamRange range(reader, byteArray.size()); + std::shared_ptr box; + Error error = Box::read(range, &box, heif_get_global_security_limits()); + REQUIRE(error == Error::Ok); + REQUIRE(range.error() == 0); + + REQUIRE(box->get_short_type() == fourcc("cmpC")); + REQUIRE(box->get_type_string() == "cmpC"); + std::shared_ptr cmpC = std::dynamic_pointer_cast(box); + REQUIRE(cmpC != nullptr); + REQUIRE(cmpC->get_compression_type() == fourcc("zlib")); + REQUIRE(cmpC->get_compressed_unit_type() == 2); + + StreamWriter writer; + Error err = cmpC->write(writer); + REQUIRE(err.error_code == heif_error_Ok); + const std::vector written = writer.get_data(); + REQUIRE(written == byteArray); + + Indent indent; + std::string dump_output = cmpC->dump(indent); + REQUIRE(dump_output == "Box: cmpC -----\nsize: 17 (header size: 12)\ncompression_type: zlib\ncompressed_entity_type: 2\n"); + +} + +TEST_CASE("cmpC_brot") { + std::vector byteArray{ + 0x00, 0x00, 0x00, 0x11, 'c', 'm', 'p', 'C', + 0x00, 0x00, 0x00, 0x00, 'b', 'r', 'o', 't', + 0x01 + }; + + auto reader = std::make_shared(byteArray.data(), + byteArray.size(), false); + + BitstreamRange range(reader, byteArray.size()); + std::shared_ptr box; + Error error = Box::read(range, &box, heif_get_global_security_limits()); + REQUIRE(error == Error::Ok); + REQUIRE(range.error() == 0); + + REQUIRE(box->get_short_type() == fourcc("cmpC")); + REQUIRE(box->get_type_string() == "cmpC"); + std::shared_ptr cmpC = std::dynamic_pointer_cast(box); + REQUIRE(cmpC != nullptr); + REQUIRE(cmpC->get_compression_type() == fourcc("brot")); + REQUIRE(cmpC->get_compressed_unit_type() == 1); + + StreamWriter writer; + Error err = cmpC->write(writer); + REQUIRE(err.error_code == heif_error_Ok); + const std::vector written = writer.get_data(); + REQUIRE(written == byteArray); + + Indent indent; + std::string dump_output = cmpC->dump(indent); + REQUIRE(dump_output == "Box: cmpC -----\nsize: 17 (header size: 12)\ncompression_type: brot\ncompressed_entity_type: 1\n"); + + } + + +TEST_CASE("icef_24_8_bit") { + std::vector byteArray{ + 0x00, 0x00, 0x00, 0x19, 'i', 'c', 'e', 'f', + 0x00, 0x00, 0x00, 0x00, + 0b01000000, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x0a, 0x03, 0x03, + 0x02, 0x03, 0x0a, 0x07 + }; + + auto reader = std::make_shared(byteArray.data(), + byteArray.size(), false); + + BitstreamRange range(reader, byteArray.size()); + std::shared_ptr box; + Error error = Box::read(range, &box, heif_get_global_security_limits()); + REQUIRE(error == Error::Ok); + REQUIRE(range.error() == 0); + + REQUIRE(box->get_short_type() == fourcc("icef")); + REQUIRE(box->get_type_string() == "icef"); + std::shared_ptr icef = std::dynamic_pointer_cast(box); + REQUIRE(icef != nullptr); + REQUIRE(icef->get_units().size() == 2); + REQUIRE(icef->get_version() == 0); + + StreamWriter writer; + Error err = icef->write(writer); + REQUIRE(err.error_code == heif_error_Ok); + const std::vector written = writer.get_data(); + REQUIRE(written == byteArray); + + Indent indent; + std::string dump_output = icef->dump(indent); + REQUIRE(dump_output == "Box: icef -----\nsize: 25 (header size: 12)\nnum_compressed_units: 2\nunit_offset: 2563, unit_size: 3\nunit_offset: 131850, unit_size: 7\n"); +} + + +TEST_CASE("icef_0_16_bit") { + std::vector byteArray{ + 0x00, 0x00, 0x00, 0x15, 'i', 'c', 'e', 'f', + 0x00, 0x00, 0x00, 0x00, + 0b00000100, + 0x00, 0x00, 0x00, 0x02, + 0x40, 0x03, + 0x0a, 0x07 + }; + + auto reader = std::make_shared(byteArray.data(), + byteArray.size(), false); + + BitstreamRange range(reader, byteArray.size()); + std::shared_ptr box; + Error error = Box::read(range, &box, heif_get_global_security_limits()); + REQUIRE(error == Error::Ok); + REQUIRE(range.error() == 0); + + REQUIRE(box->get_short_type() == fourcc("icef")); + REQUIRE(box->get_type_string() == "icef"); + std::shared_ptr icef = std::dynamic_pointer_cast(box); + REQUIRE(icef != nullptr); + REQUIRE(icef->get_units().size() == 2); + REQUIRE(icef->get_units()[0].unit_offset == 0); + REQUIRE(icef->get_units()[0].unit_size == 16387); + REQUIRE(icef->get_units()[1].unit_offset == 16387); + REQUIRE(icef->get_units()[1].unit_size == 2567); + REQUIRE(icef->get_version() == 0); + + StreamWriter writer; + Error err = icef->write(writer); + REQUIRE(err.error_code == heif_error_Ok); + const std::vector written = writer.get_data(); + REQUIRE(written == byteArray); + + Indent indent; + std::string dump_output = icef->dump(indent); + REQUIRE(dump_output == "Box: icef -----\nsize: 21 (header size: 12)\nnum_compressed_units: 2\nunit_offset: 0, unit_size: 16387\nunit_offset: 16387, unit_size: 2567\n"); +} + + +TEST_CASE("icef_32bit") { + std::vector byteArray{ + 0x00, 0x00, 0x00, 0x21, 'i', 'c', 'e', 'f', + 0x00, 0x00, 0x00, 0x00, + 0b01101100, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x03, 0x04, 0x01, 0x01, 0x02, 0x03, + 0x01, 0x02, 0x03, 0x0a, 0x00, 0x04, 0x05, 0x07 + }; + + auto reader = std::make_shared(byteArray.data(), + byteArray.size(), false); + + BitstreamRange range(reader, byteArray.size()); + std::shared_ptr box; + Error error = Box::read(range, &box, heif_get_global_security_limits()); + REQUIRE(error == Error::Ok); + REQUIRE(range.error() == 0); + + REQUIRE(box->get_short_type() == fourcc("icef")); + REQUIRE(box->get_type_string() == "icef"); + std::shared_ptr icef = std::dynamic_pointer_cast(box); + REQUIRE(icef != nullptr); + REQUIRE(icef->get_units().size() == 2); + REQUIRE(icef->get_units()[0].unit_offset == 772); + REQUIRE(icef->get_units()[0].unit_size == 16843267); + REQUIRE(icef->get_units()[1].unit_offset == 16909066); + REQUIRE(icef->get_units()[1].unit_size == 263431); + REQUIRE(icef->get_version() == 0); + + StreamWriter writer; + Error err = icef->write(writer); + REQUIRE(err.error_code == heif_error_Ok); + const std::vector written = writer.get_data(); + REQUIRE(written == byteArray); + + Indent indent; + std::string dump_output = icef->dump(indent); + REQUIRE(dump_output == "Box: icef -----\nsize: 33 (header size: 12)\nnum_compressed_units: 2\nunit_offset: 772, unit_size: 16843267\nunit_offset: 16909066, unit_size: 263431\n"); +} + + +TEST_CASE("icef_uint64") { + std::vector byteArray{ + 0x00, 0x00, 0x00, 0x31, 'i', 'c', 'e', 'f', + 0x00, 0x00, 0x00, 0x00, + 0b10010000, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x0a, 0x03, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x02, 0x03, + 0x00, 0x00, 0x00, 0x02, 0x00, 0x02, 0x03, 0x0a, + 0x00, 0x00, 0x00, 0x03, 0x00, 0x04, 0x05, 0x07 + }; + + auto reader = std::make_shared(byteArray.data(), + byteArray.size(), false); + + BitstreamRange range(reader, byteArray.size()); + std::shared_ptr box; + Error error = Box::read(range, &box, heif_get_global_security_limits()); + REQUIRE(error == Error::Ok); + REQUIRE(range.error() == 0); + + REQUIRE(box->get_short_type() == fourcc("icef")); + REQUIRE(box->get_type_string() == "icef"); + std::shared_ptr icef = std::dynamic_pointer_cast(box); + REQUIRE(icef != nullptr); + REQUIRE(icef->get_units().size() == 2); + REQUIRE(icef->get_units()[0].unit_offset == 4294969859L); + REQUIRE(icef->get_units()[0].unit_size == 8590000643L); + REQUIRE(icef->get_units()[1].unit_offset == 8590066442L); + REQUIRE(icef->get_units()[1].unit_size == 12885165319L); + REQUIRE(icef->get_version() == 0); + + StreamWriter writer; + Error err = icef->write(writer); + REQUIRE(err.error_code == heif_error_Ok); + const std::vector written = writer.get_data(); + REQUIRE(written == byteArray); + + Indent indent; + std::string dump_output = icef->dump(indent); + REQUIRE(dump_output == "Box: icef -----\nsize: 49 (header size: 12)\nnum_compressed_units: 2\nunit_offset: 4294969859, unit_size: 8590000643\nunit_offset: 8590066442, unit_size: 12885165319\n"); +} + + +TEST_CASE("icef_bad_version") { + std::vector byteArray{ + 0x00, 0x00, 0x00, 0x19, 'i', 'c', 'e', 'f', + 0x01, 0x00, 0x00, 0x00, + 0b01000000, + 0x00, 0x00, 0x00, 0x02, + 0x00, 0x0a, 0x03, 0x03, + 0x02, 0x03, 0x0a, 0x07 + }; + + auto reader = std::make_shared(byteArray.data(), + byteArray.size(), false); + + BitstreamRange range(reader, byteArray.size()); + std::shared_ptr box; + Error error = Box::read(range, &box, heif_get_global_security_limits()); + REQUIRE(error.error_code == heif_error_Unsupported_feature); + REQUIRE(error.sub_error_code == heif_suberror_Unsupported_data_version); + REQUIRE(error.message == std::string("icef box data version 1 is not implemented yet")); } diff --git a/tests/uncompressed_decode.h b/tests/uncompressed_decode.h index 4b12486676..48ea344e4b 100644 --- a/tests/uncompressed_decode.h +++ b/tests/uncompressed_decode.h @@ -46,6 +46,16 @@ "uncompressed_pix_R8G8B8_bsz0_psz10_tiled.heif", \ "uncompressed_pix_R8G8B8_bsz0_psz5_tiled.heif" +#if HAVE_BROTLI + #define BROTLI_FILES "rgb_generic_compressed_brotli.heif", +#else + #define BROTLI_FILES +#endif + +#define FILES_GENERIC_COMPRESSED \ + "rgb_generic_compressed_defl.heif", BROTLI_FILES \ + "rgb_generic_compressed_tile_deflate.heif", "rgb_generic_compressed_zlib.heif", \ + "rgb_generic_compressed_zlib_rows.heif", "rgb_generic_compressed_zlib_tiled.heif" #define FILES_16BIT_RGB \ "uncompressed_comp_B16R16G16.heif", "uncompressed_comp_B16R16G16_tiled.heif", \ diff --git a/tests/uncompressed_decode_generic_compression.cc b/tests/uncompressed_decode_generic_compression.cc new file mode 100644 index 0000000000..6a6f24fe8b --- /dev/null +++ b/tests/uncompressed_decode_generic_compression.cc @@ -0,0 +1,216 @@ +/* + libheif integration tests for uncompressed decoder + + MIT License + + Copyright (c) 2023 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 "catch.hpp" +#include "libheif/heif.h" +#include "libheif/api_structs.h" +#include +#include +#include "test_utils.h" +#include + +#include "uncompressed_decode.h" + +void check_image_size(struct heif_context *&context) { + heif_image_handle *handle = get_primary_image_handle(context); + heif_image *img = get_primary_image(handle); + + REQUIRE(heif_image_has_channel(img, heif_channel_Y) == 0); + REQUIRE(heif_image_has_channel(img, heif_channel_Cb) == 0); + REQUIRE(heif_image_has_channel(img, heif_channel_Cr) == 0); + REQUIRE(heif_image_has_channel(img, heif_channel_R) == 1); + REQUIRE(heif_image_has_channel(img, heif_channel_G) == 1); + REQUIRE(heif_image_has_channel(img, heif_channel_B) == 1); + REQUIRE(heif_image_has_channel(img, heif_channel_Alpha) == 0); + REQUIRE(heif_image_has_channel(img, heif_channel_interleaved) == 0); + int width = heif_image_get_primary_width(img); + REQUIRE(width == 128); + int height = heif_image_get_primary_height(img); + REQUIRE(height == 72); + width = heif_image_get_width(img, heif_channel_R); + REQUIRE(width == 128); + height = heif_image_get_height(img, heif_channel_R); + REQUIRE(height == 72); + width = heif_image_get_width(img, heif_channel_G); + REQUIRE(width == 128); + height = heif_image_get_height(img, heif_channel_G); + REQUIRE(height == 72); + width = heif_image_get_width(img, heif_channel_B); + REQUIRE(width == 128); + height = heif_image_get_height(img, heif_channel_B); + REQUIRE(height == 72); + + int pixel_depth = heif_image_get_bits_per_pixel(img, heif_channel_R); + REQUIRE(pixel_depth == 8); + pixel_depth = heif_image_get_bits_per_pixel(img, heif_channel_G); + REQUIRE(pixel_depth == 8); + pixel_depth = heif_image_get_bits_per_pixel(img, heif_channel_B); + REQUIRE(pixel_depth == 8); + + int pixel_range = heif_image_get_bits_per_pixel_range(img, heif_channel_R); + REQUIRE(pixel_range == 8); + pixel_range = heif_image_get_bits_per_pixel_range(img, heif_channel_G); + REQUIRE(pixel_range == 8); + pixel_range = heif_image_get_bits_per_pixel_range(img, heif_channel_B); + REQUIRE(pixel_range == 8); + + heif_image_release(img); + heif_image_handle_release(handle); +} + +TEST_CASE("check image size") { + auto file = GENERATE(FILES_GENERIC_COMPRESSED); + auto context = get_context_for_test_file(file); + INFO("file name: " << file); + check_image_size(context); + heif_context_free(context); +} + + +void check_image_content(struct heif_context *&context) { + heif_image_handle *handle = get_primary_image_handle(context); + heif_image *img = get_primary_image(handle); + + int stride; + const uint8_t *img_plane = + heif_image_get_plane_readonly(img, heif_channel_R, &stride); + REQUIRE(stride == 128); + for (int row = 0; row < 24; row++) { + INFO("row: " << row); + REQUIRE(((int)(img_plane[stride * row + 0])) == 255); + REQUIRE(((int)(img_plane[stride * row + 31])) == 255); + REQUIRE(((int)(img_plane[stride * row + 32])) == 0); + REQUIRE(((int)(img_plane[stride * row + 63])) == 0); + REQUIRE(((int)(img_plane[stride * row + 64])) == 0); + REQUIRE(((int)(img_plane[stride * row + 95])) == 0); + REQUIRE(((int)(img_plane[stride * row + 96])) == 0); + REQUIRE(((int)(img_plane[stride * row + 127])) == 0); + } + for (int row = 24; row < 48; row++) { + INFO("row: " << row); + REQUIRE(((int)(img_plane[stride * row + 0])) == 255); + REQUIRE(((int)(img_plane[stride * row + 31])) == 255); + REQUIRE(((int)(img_plane[stride * row + 32])) == 64); + REQUIRE(((int)(img_plane[stride * row + 63])) == 64); + REQUIRE(((int)(img_plane[stride * row + 64])) == 0); + REQUIRE(((int)(img_plane[stride * row + 95])) == 0); + REQUIRE(((int)(img_plane[stride * row + 96])) == 255); + REQUIRE(((int)(img_plane[stride * row + 127])) == 255); + } + for (int row = 48; row < 72; row++) { + INFO("row: " << row); + REQUIRE(((int)(img_plane[stride * row + 0])) == 192); + REQUIRE(((int)(img_plane[stride * row + 31])) == 192); + REQUIRE(((int)(img_plane[stride * row + 32])) == 255); + REQUIRE(((int)(img_plane[stride * row + 63])) == 255); + REQUIRE(((int)(img_plane[stride * row + 64])) == 255); + REQUIRE(((int)(img_plane[stride * row + 95])) == 255); + REQUIRE(((int)(img_plane[stride * row + 96])) == 255); + REQUIRE(((int)(img_plane[stride * row + 127])) == 255); + } + + img_plane = heif_image_get_plane_readonly(img, heif_channel_G, &stride); + REQUIRE(stride == 128); + for (int row = 0; row < 24; row++) { + INFO("row: " << row); + REQUIRE(((int)(img_plane[stride * row + 0])) == 0); + REQUIRE(((int)(img_plane[stride * row + 31])) == 0); + REQUIRE(((int)(img_plane[stride * row + 32])) == 255); + REQUIRE(((int)(img_plane[stride * row + 63])) == 255); + REQUIRE(((int)(img_plane[stride * row + 64])) == 0); + REQUIRE(((int)(img_plane[stride * row + 95])) == 0); + REQUIRE(((int)(img_plane[stride * row + 96])) == 0); + REQUIRE(((int)(img_plane[stride * row + 127])) == 0); + } + for (int row = 24; row < 48; row++) { + INFO("row: " << row); + REQUIRE(((int)(img_plane[stride * row + 0])) == 255); + REQUIRE(((int)(img_plane[stride * row + 31])) == 255); + REQUIRE(((int)(img_plane[stride * row + 32])) == 64); + REQUIRE(((int)(img_plane[stride * row + 63])) == 64); + REQUIRE(((int)(img_plane[stride * row + 64])) == 255); + REQUIRE(((int)(img_plane[stride * row + 95])) == 255); + REQUIRE(((int)(img_plane[stride * row + 96])) == 0); + REQUIRE(((int)(img_plane[stride * row + 127])) == 0); + } + for (int row = 48; row < 72; row++) { + INFO("row: " << row); + REQUIRE(((int)(img_plane[stride * row + 0])) == 192); + REQUIRE(((int)(img_plane[stride * row + 31])) == 192); + REQUIRE(((int)(img_plane[stride * row + 32])) == 255); + REQUIRE(((int)(img_plane[stride * row + 63])) == 255); + REQUIRE(((int)(img_plane[stride * row + 64])) == 175); + REQUIRE(((int)(img_plane[stride * row + 95])) == 175); + REQUIRE(((int)(img_plane[stride * row + 96])) == 200); + REQUIRE(((int)(img_plane[stride * row + 127])) == 200); + } + + img_plane = heif_image_get_plane_readonly(img, heif_channel_B, &stride); + REQUIRE(stride == 128); + for (int row = 0; row < 24; row++) { + INFO("row: " << row); + REQUIRE(((int)(img_plane[stride * row + 0])) == 0); + REQUIRE(((int)(img_plane[stride * row + 31])) == 0); + REQUIRE(((int)(img_plane[stride * row + 32])) == 0); + REQUIRE(((int)(img_plane[stride * row + 63])) == 0); + REQUIRE(((int)(img_plane[stride * row + 64])) == 255); + REQUIRE(((int)(img_plane[stride * row + 95])) == 255); + REQUIRE(((int)(img_plane[stride * row + 96])) == 0); + REQUIRE(((int)(img_plane[stride * row + 127])) == 0); + } + for (int row = 24; row < 48; row++) { + INFO("row: " << row); + REQUIRE(((int)(img_plane[stride * row + 0])) == 255); + REQUIRE(((int)(img_plane[stride * row + 31])) == 255); + REQUIRE(((int)(img_plane[stride * row + 32])) == 64); + REQUIRE(((int)(img_plane[stride * row + 63])) == 64); + REQUIRE(((int)(img_plane[stride * row + 64])) == 255); + REQUIRE(((int)(img_plane[stride * row + 95])) == 255); + REQUIRE(((int)(img_plane[stride * row + 96])) == 255); + REQUIRE(((int)(img_plane[stride * row + 127])) == 255); + } + for (int row = 48; row < 72; row++) { + INFO("row: " << row); + REQUIRE(((int)(img_plane[stride * row + 0])) == 192); + REQUIRE(((int)(img_plane[stride * row + 31])) == 192); + REQUIRE(((int)(img_plane[stride * row + 32])) == 0); + REQUIRE(((int)(img_plane[stride * row + 63])) == 0); + REQUIRE(((int)(img_plane[stride * row + 64])) == 175); + REQUIRE(((int)(img_plane[stride * row + 95])) == 175); + REQUIRE(((int)(img_plane[stride * row + 96])) == 0); + REQUIRE(((int)(img_plane[stride * row + 127])) == 0); + } + + heif_image_release(img); + heif_image_handle_release(handle); +} + +TEST_CASE("check image content") { + auto file = GENERATE(FILES_GENERIC_COMPRESSED); + auto context = get_context_for_test_file(file); + INFO("file name: " << file); + check_image_content(context); + heif_context_free(context); +} diff --git a/tests/uncompressed_encode.cc b/tests/uncompressed_encode.cc index b3326dc4d4..5ba24cddf0 100644 --- a/tests/uncompressed_encode.cc +++ b/tests/uncompressed_encode.cc @@ -632,6 +632,7 @@ static void do_encode(heif_image* input_image, const char* filename, bool check_ heif_context_free(ctx); } + TEST_CASE("Encode RGB") { heif_image *input_image = createImage_RGB_interleaved(); diff --git a/third-party/aom.cmd b/third-party/aom.cmd index 2156de9d15..debdeff917 100644 --- a/third-party/aom.cmd +++ b/third-party/aom.cmd @@ -10,7 +10,7 @@ : # If you're running this on Windows, be sure you've already run this (from your VC2019 install dir): : # "C:\Program Files (x86)\Microsoft Visual Studio\2019\Professional\VC\Auxiliary\Build\vcvars64.bat" -git clone -b v3.9.1 --depth 1 https://aomedia.googlesource.com/aom +git clone -b v3.10.0 --depth 1 https://aomedia.googlesource.com/aom cd aom diff --git a/third-party/rav1e.cmd b/third-party/rav1e.cmd index 6b4c3a8cc8..371f1a0685 100644 --- a/third-party/rav1e.cmd +++ b/third-party/rav1e.cmd @@ -13,7 +13,7 @@ : # Also, the error that "The target windows-msvc is not supported yet" can safely be ignored provided that rav1e/target/release : # contains rav1e.h and rav1e.lib. -git clone -b 0.5 --depth 1 https://github.com/xiph/rav1e.git +git clone -b 0.6 --depth 1 https://github.com/xiph/rav1e.git cd rav1e cargo install cargo-c diff --git a/third-party/svt.cmd b/third-party/svt.cmd index b45d56a11b..d084426e96 100644 --- a/third-party/svt.cmd +++ b/third-party/svt.cmd @@ -5,7 +5,7 @@ : # If you want to enable the SVT-AV1 encoder, please check that the WITH_SvtEnc and WITH_SvtEnc_BUILTIN CMake variables are set correctly. : # You will also have to set the PKG_CONFIG_PATH to "third-party/SVT-AV1/Build/linux/Release" so that the local SVT-AV1 library is found. -git clone -b v2.1.1 --depth 1 https://gitlab.com/AOMediaCodec/SVT-AV1.git +git clone -b v2.2.1 --depth 1 https://gitlab.com/AOMediaCodec/SVT-AV1.git cd SVT-AV1 cd Build/linux