diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml index fd5ab0b603..bab1fe3bbc 100644 --- a/.github/workflows/coverity.yml +++ b/.github/workflows/coverity.yml @@ -3,6 +3,8 @@ name: coverity on: push: branches: [ coverity ] + pull_request: + branches: [ master ] jobs: scan: @@ -10,8 +12,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 +25,7 @@ jobs: with: path: | coverity_tool.tar.gz - key: ${{ runner.os }} + key: coverity_tool-${{ runner.os }} - name: Download Coverity build tool run: | @@ -39,10 +43,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/CMakeLists.txt b/CMakeLists.txt index 465e25210d..26fcb1b4a2 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.17.6) +project(libheif LANGUAGES C CXX VERSION 1.18.0) # 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. @@ -137,6 +137,30 @@ if (WITH_UVG266) endif () endif () +# vvdec + +plugin_option(VVDEC "vvdec VVC decoder (experimental)" OFF OFF) +if (WITH_VVDEC) + # TODO: how to do configure vvdec cleanly? + find_package(Threads REQUIRED) + find_package(vvdec 2.3.0) + if (vvdec_FOUND) + set(vvdec_LIBRARIES vvdec::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 () + # dav1d plugin_option(DAV1D "Dav1d AV1 decoder" OFF ON) @@ -200,7 +224,7 @@ endif () # openjph plugin_option(OPENJPH_ENCODER "OpenJPH HT-J2K encoder" OFF ON) -# plugin_option(OPENJPH_DECODER "OpenJPH HT-J2K decoder" OFF ON) +plugin_option(OPENJPH_DECODER "OpenJPH HT-J2K decoder" OFF ON) if (WITH_OPENJPH_ENCODER OR WITH_OPENJPH_DECODER) find_package(OPENJPH) endif() @@ -226,9 +250,11 @@ plugin_compilation_info(JPEG_DECODER JPEG "JPEG decoder") plugin_compilation_info(JPEG_ENCODER JPEG "JPEG 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_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 @@ -284,9 +310,12 @@ 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 (WITH_UNCOMPRESSED_CODEC) set(SUPPORTS_UNCOMPRESSED_DECODING TRUE) @@ -297,7 +326,7 @@ message("\n=== Supported formats ===") message("format decoding encoding") format_compilation_info("HEIC" SUPPORTS_HEIC_DECODING SUPPORTS_HEIC_ENCODING) format_compilation_info("AVIF" SUPPORTS_AVIF_DECODING SUPPORTS_AVIF_ENCODING) -format_compilation_info("VVC" FALSE SUPPORTS_VVC_ENCODING) +format_compilation_info("VVC" SUPPORTS_VVC_DECODING SUPPORTS_VVC_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) @@ -366,8 +395,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) @@ -392,7 +435,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) diff --git a/CMakePresets.json b/CMakePresets.json index e449dec42e..947c37ec8d 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -45,7 +45,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 +91,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 +123,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" @@ -137,7 +137,8 @@ "CMAKE_BUILD_TYPE": "Debug", "ENABLE_PLUGIN_LOADING" : "OFF", "BUILD_TESTING" : "ON", - "WITH_REDUCED_VISIBILITY" : "OFF" + "WITH_REDUCED_VISIBILITY" : "OFF", + "WITH_UNCOMPRESSED_CODEC" : "ON" } }, { diff --git a/README.md b/README.md index 8e1840234a..d6c1835c88 100644 --- a/README.md +++ b/README.md @@ -26,14 +26,14 @@ 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 | - | 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 (experimental)| vvenc, uvg266 (experimental) | +| JPEG | libjpeg(-turbo) | libjpeg(-turbo) | +| JPEG2000 | OpenJPEG | OpenJPEG | +| uncompressed | built-in | built-in | ## API @@ -155,13 +155,13 @@ 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` +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). @@ -170,7 +170,7 @@ Further options are: * `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 @@ -305,7 +305,7 @@ This is `libheif` running in JavaScript in your browser. ## Example programs Some example programs are provided in the `examples` directory. -The program `heif-convert` converts all images stored in an HEIF/AVIF file to JPEG or PNG. +The program `heif-dec` converts all images stored in an HEIF/AVIF file to JPEG or PNG. `heif-enc` lets you convert JPEG files to HEIF/AVIF. The program `heif-info` is a simple, minimal decoder that dumps the file structure to the console. @@ -313,7 +313,7 @@ For example convert `example.heic` to JPEGs and one of the JPEGs back to HEIF: ```sh cd examples/ -./heif-convert example.heic example.jpeg +./heif-dec example.heic example.jpeg ./heif-enc example-1.jpeg -o example.heif ``` @@ -377,5 +377,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/FindUVG266.cmake b/cmake/modules/FindUVG266.cmake new file mode 100644 index 0000000000..ba2a7ec056 --- /dev/null +++ b/cmake/modules/FindUVG266.cmake @@ -0,0 +1,24 @@ +include(LibFindMacros) +libfind_pkg_check_modules(UVG266_PKGCONF uvg266) + +find_path(UVG266_INCLUDE_DIR + NAMES uvg266.h + HINTS ${UVG266_PKGCONF_INCLUDE_DIRS} ${UVG266_PKGCONF_INCLUDEDIR} + PATH_SUFFIXES UVG266 uvg266 +) + +find_library(UVG266_LIBRARY + NAMES libuvg266 uvg266 uvg266.dll + HINTS ${UVG266_PKGCONF_LIBRARY_DIRS} ${UVG266_PKGCONF_LIBDIR} +) + +set(UVG266_PROCESS_LIBS UVG266_LIBRARY) +set(UVG266_PROCESS_INCLUDES UVG266_INCLUDE_DIR) +libfind_process(UVG266) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(UVG266 + REQUIRED_VARS + UVG266_INCLUDE_DIR + UVG266_LIBRARIES +) diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 320124e96a..298775b2e7 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -1,5 +1,5 @@ # Needed to find libheif/heif_version.h while compiling the library -include_directories(${libheif_BINARY_DIR} ${libheif_SOURCE_DIR}) +include_directories(${libheif_BINARY_DIR} ${libheif_SOURCE_DIR}/libheif/api ${libheif_SOURCE_DIR}/libheif) if (MSVC) set(getopt_sources @@ -19,18 +19,25 @@ install(TARGETS heif-info RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES heif-info.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) -add_executable(heif-convert ${getopt_sources} +add_executable(heif-dec ${getopt_sources} encoder.cc encoder.h encoder_y4m.cc encoder_y4m.h - heif_convert.cc + heif_dec.cc ../libheif/exif.cc common.cc common.h) -target_link_libraries(heif-convert PRIVATE heif) -install(TARGETS heif-convert RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) -install(FILES heif-convert.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) +target_link_libraries(heif-dec PRIVATE heif) +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})") +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() add_executable(heif-enc ${getopt_sources} @@ -48,6 +55,10 @@ target_link_libraries(heif-enc PRIVATE heif) install(TARGETS heif-enc RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES heif-enc.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) +if (WITH_HEADER_COMPRESSION) + target_compile_definitions(heif-enc PRIVATE WITH_HEADER_COMPRESSION=1) +endif () + add_executable(heif-test ${getopt_sources} heif_test.cc @@ -82,10 +93,10 @@ int main() { add_definitions(-DHAVE_JPEG_WRITE_ICC_PROFILE=1) endif () - target_link_libraries(heif-convert PRIVATE JPEG::JPEG) + target_link_libraries(heif-dec PRIVATE JPEG::JPEG) target_link_libraries(heif-enc PRIVATE JPEG::JPEG) - target_sources(heif-convert PRIVATE encoder_jpeg.cc encoder_jpeg.h) + target_sources(heif-dec PRIVATE encoder_jpeg.cc encoder_jpeg.h) target_sources(heif-enc PRIVATE decoder.h decoder_jpeg.cc decoder_jpeg.h) endif () @@ -94,10 +105,10 @@ find_package(PNG) if (TARGET PNG::PNG) add_definitions(-DHAVE_LIBPNG=1) - target_link_libraries(heif-convert PRIVATE PNG::PNG) + target_link_libraries(heif-dec PRIVATE PNG::PNG) target_link_libraries(heif-enc PRIVATE PNG::PNG) - target_sources(heif-convert PRIVATE encoder_png.cc encoder_png.h) + target_sources(heif-dec PRIVATE encoder_png.cc encoder_png.h) target_sources(heif-enc PRIVATE decoder_png.cc decoder_png.h) add_executable(heif-thumbnailer ${getopt_sources} @@ -115,3 +126,30 @@ if (TARGET PNG::PNG) install(TARGETS heif-thumbnailer RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) install(FILES heif-thumbnailer.1 DESTINATION ${CMAKE_INSTALL_MANDIR}/man1) endif () + +find_package(TIFF) +if (TIFF_FOUND) + target_link_libraries(heif-enc PRIVATE TIFF::TIFF) + target_sources(heif-enc PRIVATE decoder_tiff.cc decoder_tiff.h) + target_compile_definitions(heif-enc PUBLIC HAVE_LIBTIFF=1) +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_jpeg.cc b/examples/decoder_jpeg.cc index 65b5769412..2297d4dc4d 100644 --- a/examples/decoder_jpeg.cc +++ b/examples/decoder_jpeg.cc @@ -33,7 +33,7 @@ #include #include #include "decoder_jpeg.h" -#include "libheif/exif.h" +#include "exif.h" extern "C" { // Prevent duplicate definition for libjpeg-turbo v2.0 diff --git a/examples/decoder_png.cc b/examples/decoder_png.cc index c3eeed88a8..9d30329b02 100644 --- a/examples/decoder_png.cc +++ b/examples/decoder_png.cc @@ -30,7 +30,7 @@ #include #include #include "decoder_png.h" -#include "libheif/exif.h" +#include "exif.h" extern "C" { #include diff --git a/examples/decoder_tiff.cc b/examples/decoder_tiff.cc new file mode 100644 index 0000000000..b927cb9807 --- /dev/null +++ b/examples/decoder_tiff.cc @@ -0,0 +1,394 @@ +/* + 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 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); + } +} + +InputImage loadTIFF(const char* filename) { + std::unique_ptr tifPtr(TIFFOpen(filename, "r"), [](TIFF* tif) { TIFFClose(tif); }); + if (!tifPtr) { + std::cerr << "Can't open " << filename << "\n"; + exit(1); + } + + TIFF* tif = tifPtr.get(); + if (TIFFIsTiled(tif)) { + // TODO: Implement this. + std::cerr << "Tiled TIFF images are not supported.\n"; + exit(1); + } + + InputImage input_image; + + uint16_t shortv, bpp, bps, config, format; + uint32_t width, height; + uint32_t row; + if (TIFFGetField(tif, TIFFTAG_PHOTOMETRIC, &shortv) && shortv == PHOTOMETRIC_PALETTE) { + std::cerr << "Palette TIFF images are not supported.\n"; + exit(1); + } + + if (!TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &width) || + !TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &height)) { + std::cerr << "Can't read width and/or height from TIFF image.\n"; + exit(1); + } + + TIFFGetField(tif, TIFFTAG_PLANARCONFIG, &config); + TIFFGetField(tif, TIFFTAG_SAMPLESPERPIXEL, &bpp); + if (bpp != 1 && bpp != 3 && bpp != 4) { + std::cerr << "Unsupported TIFF samples per pixel: " << bpp << "\n"; + exit(1); + } + + TIFFGetField(tif, TIFFTAG_BITSPERSAMPLE, &bps); + if (bps != 8) { + std::cerr << "Unsupported TIFF bits per sample: " << bps << "\n"; + exit(1); + } + + if (TIFFGetField(tif, TIFFTAG_SAMPLEFORMAT, &format) && format != SAMPLEFORMAT_UINT) { + std::cerr << "Unsupported TIFF sample format: " << format << "\n"; + exit(1); + } + + struct heif_error err; + struct heif_image* image = nullptr; + heif_colorspace colorspace = bpp == 1 ? heif_colorspace_monochrome : heif_colorspace_RGB; + heif_chroma chroma = bpp == 1 ? heif_chroma_monochrome : heif_chroma_interleaved_RGB; + if (bpp == 4) { + chroma = heif_chroma_interleaved_RGBA; + } + + err = heif_image_create((int) width, (int) height, colorspace, chroma, &image); + (void) err; + // TODO: handle error + + switch (config) { + case PLANARCONFIG_CONTIG: + { + heif_channel channel = heif_channel_interleaved; + heif_image_add_plane(image, channel, (int) width, (int) height, bpp*8); + + int y_stride; + uint8_t* py = heif_image_get_plane(image, channel, &y_stride); + + tdata_t buf = _TIFFmalloc(TIFFScanlineSize(tif)); + for (row = 0; row < height; row++) { + TIFFReadScanline(tif, buf, row, 0); + memcpy(py, buf, width*bpp); + py += y_stride; + } + _TIFFfree(buf); + } + break; + case PLANARCONFIG_SEPARATE: + { + heif_channel channel = heif_channel_interleaved; + heif_image_add_plane(image, channel, (int) width, (int) height, bpp*8); + + int y_stride; + uint8_t* py = heif_image_get_plane(image, channel, &y_stride); + + if (bpp == 4) { + TIFFRGBAImage img; + char emsg[1024] = { 0 }; + if (!TIFFRGBAImageBegin(&img, tif, 1, emsg)) { + heif_image_release(image); + std::cerr << "Could not get RGBA image: " << emsg << "\n"; + exit(1); + } + + uint32_t* buf = static_cast(_TIFFmalloc(width*bpp)); + for (row = 0; row < height; row++) { + TIFFReadRGBAStrip(tif, row, buf); + memcpy(py, buf, width*bpp); + py += y_stride; + } + _TIFFfree(buf); + TIFFRGBAImageEnd(&img); + } else { + uint8_t* buf = static_cast(_TIFFmalloc(TIFFScanlineSize(tif))); + for (uint16_t i = 0; i < bpp; i++) { + uint8_t* dest = py+i; + for (row = 0; row < height; row++) { + TIFFReadScanline(tif, buf, row, i); + for (uint32_t x = 0; x < width; x++, dest+=bpp) { + *dest = buf[x]; + } + dest += (y_stride - width*bpp); + } + } + _TIFFfree(buf); + } + } + break; + default: + heif_image_release(image); + std::cerr << "Unsupported planar config: " << config << "\n"; + exit(1); + } + + 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 input_image; +} diff --git a/examples/decoder_tiff.h b/examples/decoder_tiff.h new file mode 100644 index 0000000000..44a26fe5af --- /dev/null +++ b/examples/decoder_tiff.h @@ -0,0 +1,34 @@ +/* + 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" + +InputImage loadTIFF(const char* filename); + +#endif //LIBHEIF_DECODER_TIFF_H diff --git a/examples/encoder_jpeg.cc b/examples/encoder_jpeg.cc index b82f8aa39b..529ab9409d 100644 --- a/examples/encoder_jpeg.cc +++ b/examples/encoder_jpeg.cc @@ -32,7 +32,7 @@ #include #include "encoder_jpeg.h" -#include "libheif/exif.h" +#include "exif.h" #define JPEG_XMP_MARKER (JPEG_APP0+1) /* JPEG marker code for XMP */ #define JPEG_XMP_MARKER_ID "http://ns.adobe.com/xap/1.0/" diff --git a/examples/encoder_png.cc b/examples/encoder_png.cc index 8b918f96e9..f89438a5c7 100644 --- a/examples/encoder_png.cc +++ b/examples/encoder_png.cc @@ -30,7 +30,7 @@ #include #include "encoder_png.h" -#include "libheif/exif.h" +#include "exif.h" PngEncoder::PngEncoder() = default; diff --git a/examples/example.heic b/examples/example.heic index 91d5dc1497..8293840378 100644 Binary files a/examples/example.heic and b/examples/example.heic differ diff --git a/examples/heif-convert.1 b/examples/heif-dec.1 similarity index 92% rename from examples/heif-convert.1 rename to examples/heif-dec.1 index 5dc81ca898..da55eae503 100644 --- a/examples/heif-convert.1 +++ b/examples/heif-dec.1 @@ -1,13 +1,13 @@ .TH HEIF-CONVERT 1 .SH NAME -heif-convert \- convert HEIC/HEIF image +heif-dec \- decode HEIC/HEIF image .SH SYNOPSIS -.B heif-convert +.B heif-dec [\fB\-q\fR \fIQUALITY\fR] .IR filename .IR output[.jpg|.png|.y4m] .SH DESCRIPTION -.B heif-convert +.B heif-dec Convert HEIC/HEIF image to a different image format. .SH OPTIONS .TP diff --git a/examples/heif_convert.cc b/examples/heif_dec.cc similarity index 98% rename from examples/heif_convert.cc rename to examples/heif_dec.cc index 0e0e0fff15..da6e1533bd 100644 --- a/examples/heif_convert.cc +++ b/examples/heif_dec.cc @@ -69,9 +69,9 @@ static void show_help(const char* argv0) { - std::cerr << " heif-convert libheif version: " << heif_get_version() << "\n" - << "-------------------------------------------\n" - "Usage: heif-convert [options] [output-image]\n" + std::cerr << " " << argv0 << " libheif version: " << heif_get_version() << "\n" + << "---------------------------------------\n" + "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" @@ -631,17 +631,17 @@ int main(int argc, char** argv) heif_image_handle_release_auxiliary_type(aux_handle, &auxTypeC); + if (option_no_colons) { + std::replace(auxType.begin(), auxType.end(), ':', '_'); + } + 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"); diff --git a/examples/heif_enc.cc b/examples/heif_enc.cc index 69c8441c58..1329d58965 100644 --- a/examples/heif_enc.cc +++ b/examples/heif_enc.cc @@ -48,11 +48,15 @@ #include "decoder_png.h" #endif +#if HAVE_LIBTIFF +#include "decoder_tiff.h" +#endif + #include "decoder_y4m.h" #include #include "benchmark.h" -#include "libheif/exif.h" +#include "exif.h" #include "common.h" int master_alpha = 1; @@ -181,7 +185,9 @@ void show_help(const char* argv0) << " --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" @@ -211,6 +217,17 @@ InputImage loadPNG(const char* filename, int output_bit_depth) #endif +#if !HAVE_LIBTIFF +InputImage loadTIFF(const char* filename) +{ + std::cerr << "Cannot load TIFF because libtiff support was not compiled.\n"; + exit(1); + + return {}; +} +#endif + + void list_encoder_parameters(heif_encoder* encoder) { std::cerr << "Parameters for encoder `" << heif_encoder_get_name(encoder) << "`:\n"; @@ -629,7 +646,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"; } @@ -787,13 +804,15 @@ int main(int argc, char** argv) enum { - PNG, JPEG, Y4M + 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; @@ -803,6 +822,9 @@ int main(int argc, char** argv) else if (filetype == Y4M) { input_image = loadY4M(input_filename.c_str()); } + else if (filetype == TIFF) { + input_image = loadTIFF(input_filename.c_str()); + } else { input_image = loadJPEG(input_filename.c_str()); } diff --git a/fuzzing/CMakeLists.txt b/fuzzing/CMakeLists.txt index 0aeb63064b..02fb513803 100644 --- a/fuzzing/CMakeLists.txt +++ b/fuzzing/CMakeLists.txt @@ -1,4 +1,4 @@ -include_directories(${libheif_BINARY_DIR} ${libheif_SOURCE_DIR}) +include_directories(${libheif_BINARY_DIR} ${libheif_SOURCE_DIR}/libheif ${libheif_SOURCE_DIR}/libheif/api) add_executable(box_fuzzer box_fuzzer.cc) diff --git a/fuzzing/box_fuzzer.cc b/fuzzing/box_fuzzer.cc index ac95175545..f47e9ae111 100644 --- a/fuzzing/box_fuzzer.cc +++ b/fuzzing/box_fuzzer.cc @@ -20,9 +20,9 @@ #include -#include "libheif/box.h" -#include "libheif/bitstream.h" -#include "libheif/logging.h" +#include "box.h" +#include "bitstream.h" +#include "logging.h" extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { diff --git a/fuzzing/color_conversion_fuzzer.cc b/fuzzing/color_conversion_fuzzer.cc index 2901d6ffea..3de50ffeaa 100644 --- a/fuzzing/color_conversion_fuzzer.cc +++ b/fuzzing/color_conversion_fuzzer.cc @@ -22,9 +22,9 @@ #include -#include "libheif/bitstream.h" -#include "libheif/color-conversion/colorconversion.h" -#include "libheif/pixelimage.h" +#include "bitstream.h" +#include "color-conversion/colorconversion.h" +#include "pixelimage.h" static bool is_valid_chroma(uint8_t 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/gdk-pixbuf/CMakeLists.txt b/gdk-pixbuf/CMakeLists.txt index eeb2727d05..de67e79624 100644 --- a/gdk-pixbuf/CMakeLists.txt +++ b/gdk-pixbuf/CMakeLists.txt @@ -10,7 +10,7 @@ if(UNIX OR MINGW) add_library(pixbufloader-heif MODULE pixbufloader-heif.c) - target_include_directories(pixbufloader-heif PRIVATE ${GDKPIXBUF2_INCLUDE_DIRS} ${libheif_BINARY_DIR} ${libheif_SOURCE_DIR}) + target_include_directories(pixbufloader-heif PRIVATE ${GDKPIXBUF2_INCLUDE_DIRS} ${libheif_BINARY_DIR} ${libheif_SOURCE_DIR}/libheif/api) target_link_directories(pixbufloader-heif PRIVATE ${GDKPIXBUF2_LIBRARY_DIRS}) diff --git a/go/heif/heif.go b/go/heif/heif.go index ad2fa5b641..85706b84cb 100644 --- a/go/heif/heif.go +++ b/go/heif/heif.go @@ -251,6 +251,8 @@ const ( SuberrorCannotReadPluginDirectory = C.heif_suberror_Cannot_read_plugin_directory + SuberrorNoMatchingDecoderInstalled = C.heif_suberror_No_matching_decoder_installed + SuberrorNoOrInvalidPrimaryItem = C.heif_suberror_No_or_invalid_primary_item SuberrorNoInfeBox = C.heif_suberror_No_infe_box @@ -263,6 +265,12 @@ const ( SuberrorInvalidImageSize = C.heif_suberror_Invalid_image_size + SuberrorCameraIntrinsicMatrixUndefined = C.heif_suberror_Camera_intrinsic_matrix_undefined + + SuberrorCameraExtrinsicMatrixUndefined = C.heif_suberror_Camera_extrinsic_matrix_undefined + + SuberrorDecompressionInvalidData = C.heif_suberror_Decompression_invalid_data + // --- Memory_allocation_error --- // A security limit preventing unreasonable memory allocations was exceeded by the input file. @@ -270,6 +278,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. @@ -301,6 +311,8 @@ const ( SuberrorInvalidRegionData = C.heif_suberror_Invalid_region_data + SuberrorNoIspeProperty = C.heif_suberror_No_ispe_property + SuberrorWrongTileImagePixelDepth = C.heif_suberror_Wrong_tile_image_pixel_depth SuberrorUnknownNCLXColorPrimaries = C.heif_suberror_Unknown_NCLX_color_primaries @@ -309,7 +321,11 @@ const ( SuberrorUnknownNCLXMatrixCoefficients = C.heif_suberror_Unknown_NCLX_matrix_coefficients - SuberrorInvalidJPEG2000Codestream = C.heif_suberror_Invalid_J2K_codestream + SuberrorInvalidJ2KCodestream = C.heif_suberror_Invalid_J2K_codestream + + SuberrorNoVcCBox = C.heif_suberror_No_vvcC_box + + SuberrorNoIcbrBox = C.heif_suberror_No_icbr_box // --- Unsupported_feature --- @@ -321,6 +337,8 @@ const ( SuberrorUnsupportedDataVersion = C.heif_suberror_Unsupported_data_version + SuberrorUnsupportedGenericCompressionMethod = C.heif_suberror_Unsupported_generic_compression_method + // The conversion of the source image to the requested chroma / colorspace is not supported. SuberrorUnsupportedColorConversion = C.heif_suberror_Unsupported_color_conversion diff --git a/libheif/CMakeLists.txt b/libheif/CMakeLists.txt index 7d15a465f1..b638a21725 100644 --- a/libheif/CMakeLists.txt +++ b/libheif/CMakeLists.txt @@ -1,60 +1,67 @@ include(CMakePackageConfigHelpers) -configure_file(heif_version.h.in ${CMAKE_CURRENT_BINARY_DIR}/heif_version.h) +configure_file(api/libheif/heif_version.h.in ${CMAKE_CURRENT_BINARY_DIR}/heif_version.h) set(libheif_headers - heif.h - heif_cxx.h - heif_plugin.h - heif_properties.h - heif_regions.h - heif_items.h + api/libheif/heif.h + api/libheif/heif_cxx.h + api/libheif/heif_plugin.h + api/libheif/heif_properties.h + api/libheif/heif_regions.h + api/libheif/heif_items.h ${CMAKE_CURRENT_BINARY_DIR}/heif_version.h) set(libheif_sources bitstream.cc + bitstream.h box.cc + box.h error.cc - heif.cc + error.h context.cc + context.h file.cc + file.h pixelimage.cc - hevc.cc - avif.cc + pixelimage.h plugin_registry.cc - heif_plugin.cc nclx.cc - bitstream.h - box.h - error.h - api_structs.h - context.h - file.h - pixelimage.h - hevc.h - avif.h + nclx.h plugin_registry.h security_limits.h - init.cc init.h - nclx.h + init.cc + init.h logging.h logging.cc - mask_image.cc - mask_image.h - metadata_compression.cc - metadata_compression.h + compression.h + compression_brotli.cc + compression_zlib.cc common_utils.cc common_utils.h region.cc region.h - heif_regions.h - heif_regions.cc - heif_properties.h - heif_properties.cc - heif_items.h - heif_items.cc - heif_experimental.h - heif_experimental.cc + api/libheif/api_structs.h + api/libheif/heif.cc + api/libheif/heif_regions.cc + 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 color-conversion/colorconversion.cc color-conversion/colorconversion.h color-conversion/rgb2yuv.cc @@ -73,14 +80,6 @@ set(libheif_sources color-conversion/alpha.h color-conversion/chroma_sampling.cc color-conversion/chroma_sampling.h - jpeg.h - jpeg.cc - jpeg2000.h - jpeg2000.cc - vvc.h - vvc.cc - avc.h - avc.cc ${libheif_headers}) add_library(heif ${libheif_sources}) @@ -94,7 +93,7 @@ if (ENABLE_PLUGIN_LOADING) endif () # Needed to find libheif/heif_version.h while compiling the library -target_include_directories(heif PRIVATE ${libheif_BINARY_DIR} ${libheif_SOURCE_DIR}) +target_include_directories(heif PRIVATE ${libheif_BINARY_DIR} ${libheif_SOURCE_DIR}/libheif ${libheif_SOURCE_DIR}/libheif/api) # Propagate include/libheif to consume the headers from other projects target_include_directories(heif @@ -135,12 +134,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}) @@ -154,10 +158,10 @@ endif () if (WITH_UNCOMPRESSED_CODEC) target_compile_definitions(heif PUBLIC WITH_UNCOMPRESSED_CODEC=1) target_sources(heif PRIVATE - uncompressed_box.h - uncompressed_box.cc - uncompressed_image.h - uncompressed_image.cc) + codecs/uncompressed_box.h + codecs/uncompressed_box.cc + codecs/uncompressed_image.h + codecs/uncompressed_image.cc) endif () write_basic_package_version_file(${PROJECT_NAME}-config-version.cmake COMPATIBILITY ExactVersion) @@ -174,3 +178,14 @@ install(EXPORT ${PROJECT_NAME}-config DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}") + + +# --- on Windows, copy the DLL into the executable directory for easier development + +if (WIN32 AND BUILD_SHARED_LIBS) + add_custom_command(TARGET heif POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + $ + $/../examples + ) +endif () diff --git a/libheif/Doxyfile.in b/libheif/Doxyfile.in index 57aaa6523e..52d7ff6e1a 100644 --- a/libheif/Doxyfile.in +++ b/libheif/Doxyfile.in @@ -93,14 +93,6 @@ ALLOW_UNICODE_NAMES = NO OUTPUT_LANGUAGE = English -# The OUTPUT_TEXT_DIRECTION tag is used to specify the direction in which all -# documentation generated by doxygen is written. Doxygen will use this -# information to generate all generated output in the proper direction. -# Possible values are: None, LTR, RTL and Context. -# The default value is: None. - -OUTPUT_TEXT_DIRECTION = None - # If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member # descriptions after the members that are listed in the file and class # documentation (similar to Javadoc). Set to NO to disable this. @@ -864,9 +856,9 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = @CMAKE_CURRENT_SOURCE_DIR@/libheif/heif.h \ -@CMAKE_CURRENT_SOURCE_DIR@/libheif/heif_items.h \ -@CMAKE_CURRENT_SOURCE_DIR@/libheif/heif_regions.h +INPUT = @CMAKE_CURRENT_SOURCE_DIR@/libheif/api/libheif/heif.h \ +@CMAKE_CURRENT_SOURCE_DIR@/libheif/api/libheif/heif_items.h \ +@CMAKE_CURRENT_SOURCE_DIR@/libheif/api/libheif/heif_regions.h # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -1327,15 +1319,6 @@ HTML_COLORSTYLE_SAT = 100 HTML_COLORSTYLE_GAMMA = 80 -# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML -# page will contain the date and time when the page was generated. Setting this -# to YES can help to show when doxygen was last run and thus if the -# documentation is up to date. -# The default value is: NO. -# This tag requires that the tag GENERATE_HTML is set to YES. - -HTML_TIMESTAMP = NO - # If the HTML_DYNAMIC_MENUS tag is set to YES then the generated HTML # documentation will contain a main index with vertical navigation menus that # are dynamically created via JavaScript. If disabled, the navigation index will @@ -1630,17 +1613,6 @@ HTML_FORMULA_FORMAT = png FORMULA_FONTSIZE = 10 -# Use the FORMULA_TRANSPARENT tag to determine whether or not the images -# generated for formulas are transparent PNGs. Transparent PNGs are not -# supported properly for IE 6.0, but are supported on all modern browsers. -# -# Note that when changing this option you need to delete any form_*.png files in -# the HTML output directory before the changes have effect. -# The default value is: YES. -# This tag requires that the tag GENERATE_HTML is set to YES. - -FORMULA_TRANSPARENT = YES - # The FORMULA_MACROFILE can contain LaTeX \newcommand and \renewcommand commands # to create new LaTeX commands to be used in formulas as building blocks. See # the section "Including formulas" for details. @@ -1944,16 +1916,6 @@ LATEX_BATCHMODE = NO LATEX_HIDE_INDICES = NO -# If the LATEX_SOURCE_CODE tag is set to YES then doxygen will include source -# code with syntax highlighting in the LaTeX output. -# -# Note that which sources are shown also depends on other settings such as -# SOURCE_BROWSER. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_SOURCE_CODE = NO - # The LATEX_BIB_STYLE tag can be used to specify the style to use for the # bibliography, e.g. plainnat, or ieeetr. See # https://en.wikipedia.org/wiki/BibTeX and \cite for more info. @@ -1962,14 +1924,6 @@ LATEX_SOURCE_CODE = NO LATEX_BIB_STYLE = plain -# If the LATEX_TIMESTAMP tag is set to YES then the footer of each generated -# page will contain the date and time when the page was generated. Setting this -# to NO can help when comparing the output of multiple runs. -# The default value is: NO. -# This tag requires that the tag GENERATE_LATEX is set to YES. - -LATEX_TIMESTAMP = NO - # The LATEX_EMOJI_DIRECTORY tag is used to specify the (relative or absolute) # path from which the emoji images will be read. If a relative path is entered, # it will be relative to the LATEX_OUTPUT directory. If left blank the @@ -2034,16 +1988,6 @@ RTF_STYLESHEET_FILE = RTF_EXTENSIONS_FILE = -# If the RTF_SOURCE_CODE tag is set to YES then doxygen will include source code -# with syntax highlighting in the RTF output. -# -# Note that which sources are shown also depends on other settings such as -# SOURCE_BROWSER. -# The default value is: NO. -# This tag requires that the tag GENERATE_RTF is set to YES. - -RTF_SOURCE_CODE = NO - #--------------------------------------------------------------------------- # Configuration options related to the man page output #--------------------------------------------------------------------------- @@ -2140,15 +2084,6 @@ GENERATE_DOCBOOK = NO DOCBOOK_OUTPUT = docbook -# If the DOCBOOK_PROGRAMLISTING tag is set to YES, doxygen will include the -# program listings (including syntax highlighting and cross-referencing -# information) to the DOCBOOK output. Note that enabling this will significantly -# increase the size of the DOCBOOK output. -# The default value is: NO. -# This tag requires that the tag GENERATE_DOCBOOK is set to YES. - -DOCBOOK_PROGRAMLISTING = NO - #--------------------------------------------------------------------------- # Configuration options for the AutoGen Definitions output #--------------------------------------------------------------------------- @@ -2327,15 +2262,6 @@ EXTERNAL_PAGES = YES # Configuration options related to the dot tool #--------------------------------------------------------------------------- -# If the CLASS_DIAGRAMS tag is set to YES, doxygen will generate a class diagram -# (in HTML and LaTeX) for classes with base or super classes. Setting the tag to -# NO turns the diagrams off. Note that this option also works with HAVE_DOT -# disabled, but it is recommended to install and use dot, since it yields more -# powerful graphs. -# The default value is: YES. - -CLASS_DIAGRAMS = YES - # You can include diagrams made with dia in doxygen documentation. Doxygen will # then run dia to produce the diagram and insert it in the documentation. The # DIA_PATH tag allows you to specify the directory where the dia binary resides. @@ -2368,22 +2294,6 @@ HAVE_DOT = YES DOT_NUM_THREADS = 0 -# When you want a differently looking font in the dot files that doxygen -# generates you can specify the font name using DOT_FONTNAME. You need to make -# sure dot is able to find the font, which can be done by putting it in a -# standard location or by setting the DOTFONTPATH environment variable or by -# setting DOT_FONTPATH to the directory containing the font. -# The default value is: Helvetica. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_FONTNAME = Helvetica - -# The DOT_FONTSIZE tag can be used to set the size (in points) of the font of -# dot graphs. -# Minimum value: 4, maximum value: 24, default value: 10. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_FONTSIZE = 10 # By default doxygen will tell dot to use the default font as specified with # DOT_FONTNAME. If you specify a different font using DOT_FONTNAME you can set @@ -2621,18 +2531,6 @@ DOT_GRAPH_MAX_NODES = 50 MAX_DOT_GRAPH_DEPTH = 0 -# Set the DOT_TRANSPARENT tag to YES to generate images with a transparent -# background. This is disabled by default, because dot on Windows does not seem -# to support this out of the box. -# -# Warning: Depending on the platform used, enabling this option may lead to -# badly anti-aliased labels on the edges of a graph (i.e. they become hard to -# read). -# The default value is: NO. -# This tag requires that the tag HAVE_DOT is set to YES. - -DOT_TRANSPARENT = NO - # Set the DOT_MULTI_TARGETS tag to YES to allow dot to generate multiple output # files in one run (i.e. multiple -o and -T options on the command line). This # makes dot run faster, but since only newer versions of dot (>1.8.10) support diff --git a/libheif/api_structs.h b/libheif/api/libheif/api_structs.h similarity index 100% rename from libheif/api_structs.h rename to libheif/api/libheif/api_structs.h diff --git a/libheif/heif.cc b/libheif/api/libheif/heif.cc similarity index 95% rename from libheif/heif.cc rename to libheif/api/libheif/heif.cc index 5212f7bc68..980b0434b0 100644 --- a/libheif/heif.cc +++ b/libheif/api/libheif/heif.cc @@ -18,9 +18,9 @@ * along with libheif. If not, see . */ -#include "libheif/heif_plugin.h" -#include "libheif/region.h" -#include "libheif/common_utils.h" +#include "heif_plugin.h" +#include "region.h" +#include "common_utils.h" #include #include "heif.h" #include "file.h" @@ -363,10 +363,11 @@ struct heif_error heif_list_compatible_brands(const uint8_t* data, int len, heif } auto brands = ftyp->list_brands(); - *out_brands = (heif_brand2*) malloc(sizeof(heif_brand2) * brands.size()); - *out_size = (int) brands.size(); + size_t nBrands = brands.size(); + *out_brands = (heif_brand2*) malloc(sizeof(heif_brand2) * nBrands); + *out_size = (int)nBrands; - for (int i = 0; i < (int) brands.size(); i++) { + for (size_t i = 0; i < nBrands; i++) { (*out_brands)[i] = brands[i]; } @@ -1668,8 +1669,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); } @@ -1709,8 +1710,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); } @@ -1746,8 +1747,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); } @@ -2734,7 +2735,7 @@ int heif_encoder_has_default(struct heif_encoder* encoder, static void set_default_options(heif_encoding_options& options) { - options.version = 6; + options.version = 7; options.save_alpha_channel = true; options.macOS_compatibility_workaround = false; @@ -2747,11 +2748,16 @@ static void set_default_options(heif_encoding_options& options) options.color_conversion_options.preferred_chroma_downsampling_algorithm = heif_chroma_downsampling_average; options.color_conversion_options.preferred_chroma_upsampling_algorithm = heif_chroma_upsampling_bilinear; options.color_conversion_options.only_use_preferred_chroma_algorithm = false; + + options.prefer_uncC_short_form = true; } static void copy_options(heif_encoding_options& options, const heif_encoding_options& input_options) { switch (input_options.version) { + case 7: + options.prefer_uncC_short_form = input_options.prefer_uncC_short_form; + // fallthrough case 6: options.color_conversion_options = input_options.color_conversion_options; // fallthrough @@ -2848,6 +2854,76 @@ struct heif_error heif_context_encode_image(struct heif_context* ctx, } +struct heif_error heif_context_encode_grid(struct heif_context* ctx, + struct heif_image** tiles, + uint16_t columns, + uint16_t rows, + struct heif_encoder* encoder, + const struct heif_encoding_options* input_options, + struct heif_image_handle** out_image_handle) +{ + if (!encoder || !tiles) { + return Error(heif_error_Usage_error, + heif_suberror_Null_pointer_argument).error_struct(ctx->context.get()); + } + else if (rows == 0 || columns == 0) { + return Error(heif_error_Usage_error, + heif_suberror_Invalid_parameter_value).error_struct(ctx->context.get()); + } + + // TODO: Don't repeat this code from heif_context_encode_image() + heif_encoding_options options; + heif_color_profile_nclx nclx; + set_default_options(options); + if (input_options) { + copy_options(options, *input_options); + + if (options.output_nclx_profile == nullptr) { + auto input_nclx = tiles[0]->image->get_color_profile_nclx(); + if (input_nclx) { + options.output_nclx_profile = &nclx; + nclx.version = 1; + nclx.color_primaries = (enum heif_color_primaries) input_nclx->get_colour_primaries(); + nclx.transfer_characteristics = (enum heif_transfer_characteristics) input_nclx->get_transfer_characteristics(); + nclx.matrix_coefficients = (enum heif_matrix_coefficients) input_nclx->get_matrix_coefficients(); + nclx.full_range_flag = input_nclx->get_full_range_flag(); + } + } + } + + // Convert heif_images to a vector of HeifPixelImages + std::vector> pixel_tiles; + for (int i=0; iimage); + } + + // 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()); + } + + // Mark as primary image + if (ctx->context->is_primary_image_set() == false) { + ctx->context->set_primary_image(out_grid); + } + + if (out_image_handle) { + *out_image_handle = new heif_image_handle; + (*out_image_handle)->image = out_grid; + (*out_image_handle)->context = ctx->context; + } + + return heif_error_success; +} + + 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) diff --git a/libheif/heif.h b/libheif/api/libheif/heif.h similarity index 97% rename from libheif/heif.h rename to libheif/api/libheif/heif.h index a80d333f2c..44ded7482c 100644 --- a/libheif/heif.h +++ b/libheif/api/libheif/heif.h @@ -241,6 +241,11 @@ 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, + + // Decompressing generic compression or header compression data failed (e.g. bitstream corruption) + heif_suberror_Decompression_invalid_data = 150, // --- Memory_allocation_error --- @@ -249,6 +254,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 +305,8 @@ 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, // --- Encoder_plugin_error --- @@ -2090,6 +2100,11 @@ struct heif_encoding_options // version 6 options struct heif_color_conversion_options color_conversion_options; + + // version 7 options + + // Set this to true to use compressed form of uncC where possible + uint8_t prefer_uncC_short_form; }; LIBHEIF_API @@ -2111,6 +2126,27 @@ struct heif_error heif_context_encode_image(struct heif_context*, const struct heif_encoding_options* options, struct heif_image_handle** out_image_handle); +/** + * @brief Encodes an array of images into a grid. + * + * @param ctx The file context + * @param tiles User allocated array of images that will form the grid. + * @param rows The number of rows in the grid. + * @param columns The number of columns in the grid. + * @param encoder Defines the encoder to use. See heif_context_get_encoder_for_format() + * @param input_options Optional, may be nullptr. + * @param out_image_handle Returns a handle to the grid. The caller is responsible for freeing it. + * @return Returns an error if ctx, tiles, or encoder is nullptr. If rows or columns is 0. + */ +LIBHEIF_API +struct heif_error heif_context_encode_grid(struct heif_context* ctx, + struct heif_image** tiles, + uint16_t rows, + uint16_t columns, + struct heif_encoder* encoder, + const struct heif_encoding_options* input_options, + struct heif_image_handle** out_image_handle); + LIBHEIF_API struct heif_error heif_context_set_primary_image(struct heif_context*, struct heif_image_handle* image_handle); @@ -2133,10 +2169,12 @@ struct heif_error heif_context_encode_thumbnail(struct heif_context*, enum heif_metadata_compression { - heif_metadata_compression_off, - heif_metadata_compression_auto, - heif_metadata_compression_deflate, - heif_metadata_compression_unknown + 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 }; // Assign 'thumbnail_image' as the thumbnail image of 'master_image'. diff --git a/libheif/heif_cxx.h b/libheif/api/libheif/heif_cxx.h similarity index 100% rename from libheif/heif_cxx.h rename to libheif/api/libheif/heif_cxx.h diff --git a/libheif/heif_emscripten.h b/libheif/api/libheif/heif_emscripten.h similarity index 90% rename from libheif/heif_emscripten.h rename to libheif/api/libheif/heif_emscripten.h index b91c5fc504..5b015f1288 100644 --- a/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) @@ -336,6 +366,7 @@ EMSCRIPTEN_BINDINGS(libheif) { .value("heif_suberror_Plugin_loading_error", heif_suberror_Plugin_loading_error) .value("heif_suberror_Auxiliary_image_type_unspecified", heif_suberror_Auxiliary_image_type_unspecified) .value("heif_suberror_Cannot_read_plugin_directory", heif_suberror_Cannot_read_plugin_directory) + .value("heif_suberror_No_matching_decoder_installed", heif_suberror_No_matching_decoder_installed) .value("heif_suberror_No_or_invalid_primary_item", heif_suberror_No_or_invalid_primary_item) .value("heif_suberror_No_infe_box", heif_suberror_No_infe_box) .value("heif_suberror_Security_limit_exceeded", heif_suberror_Security_limit_exceeded) @@ -354,10 +385,10 @@ EMSCRIPTEN_BINDINGS(libheif) { .value("heif_suberror_Item_reference_cycle", heif_suberror_Item_reference_cycle) .value("heif_suberror_Invalid_pixi_box", heif_suberror_Invalid_pixi_box) .value("heif_suberror_Invalid_region_data", heif_suberror_Invalid_region_data) - .value("heif_suberror_Invalid_J2K_codestream", heif_suberror_Invalid_J2K_codestream) .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_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) @@ -365,7 +396,13 @@ EMSCRIPTEN_BINDINGS(libheif) { .value("heif_suberror_Wrong_tile_image_pixel_depth", heif_suberror_Wrong_tile_image_pixel_depth) .value("heif_suberror_Unknown_NCLX_color_primaries", heif_suberror_Unknown_NCLX_color_primaries) .value("heif_suberror_Unknown_NCLX_transfer_characteristics", heif_suberror_Unknown_NCLX_transfer_characteristics) - .value("heif_suberror_Unknown_NCLX_matrix_coefficients", heif_suberror_Unknown_NCLX_matrix_coefficients); + .value("heif_suberror_Unknown_NCLX_matrix_coefficients", heif_suberror_Unknown_NCLX_matrix_coefficients) + .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_No_icbr_box", heif_suberror_No_icbr_box); + emscripten::enum_("heif_compression_format") .value("heif_compression_undefined", heif_compression_undefined) .value("heif_compression_HEVC", heif_compression_HEVC) @@ -414,6 +451,11 @@ EMSCRIPTEN_BINDINGS(libheif) { .value("heif_channel_B", heif_channel_B) .value("heif_channel_Alpha", heif_channel_Alpha) .value("heif_channel_interleaved", heif_channel_interleaved); + 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/heif_experimental.cc b/libheif/api/libheif/heif_experimental.cc similarity index 99% rename from libheif/heif_experimental.cc rename to libheif/api/libheif/heif_experimental.cc index 3b89a6f82c..ed850347c9 100644 --- a/libheif/heif_experimental.cc +++ b/libheif/api/libheif/heif_experimental.cc @@ -18,7 +18,7 @@ * along with libheif. If not, see . */ -#include "libheif/heif_experimental.h" +#include "heif_experimental.h" #include "context.h" #include "api_structs.h" #include "file.h" diff --git a/libheif/heif_experimental.h b/libheif/api/libheif/heif_experimental.h similarity index 100% rename from libheif/heif_experimental.h rename to libheif/api/libheif/heif_experimental.h diff --git a/libheif/heif_items.cc b/libheif/api/libheif/heif_items.cc similarity index 99% rename from libheif/heif_items.cc rename to libheif/api/libheif/heif_items.cc index 06a6d7ac55..dd96dd4ae4 100644 --- a/libheif/heif_items.cc +++ b/libheif/api/libheif/heif_items.cc @@ -18,7 +18,7 @@ * along with libheif. If not, see . */ -#include "libheif/heif_items.h" +#include "heif_items.h" #include "context.h" #include "api_structs.h" #include "file.h" diff --git a/libheif/heif_items.h b/libheif/api/libheif/heif_items.h similarity index 99% rename from libheif/heif_items.h rename to libheif/api/libheif/heif_items.h index eecaf0733c..baca69f0aa 100644 --- a/libheif/heif_items.h +++ b/libheif/api/libheif/heif_items.h @@ -158,7 +158,7 @@ struct heif_error heif_item_get_item_data(const struct heif_context* ctx, * Free the item data. * * This is used to free memory associated with the data returned by - * {@link heif_context_get_item_data} in 'out_data' and set the pointer to NULL. + * {@link heif_item_get_item_data} in 'out_data' and set the pointer to NULL. * * @param ctx the file context * @param item_data the data to free diff --git a/libheif/heif_plugin.cc b/libheif/api/libheif/heif_plugin.cc similarity index 100% rename from libheif/heif_plugin.cc rename to libheif/api/libheif/heif_plugin.cc diff --git a/libheif/heif_plugin.h b/libheif/api/libheif/heif_plugin.h similarity index 100% rename from libheif/heif_plugin.h rename to libheif/api/libheif/heif_plugin.h diff --git a/libheif/heif_properties.cc b/libheif/api/libheif/heif_properties.cc similarity index 87% rename from libheif/heif_properties.cc rename to libheif/api/libheif/heif_properties.cc index a2259ffde8..5f99d28d4e 100644 --- a/libheif/heif_properties.cc +++ b/libheif/api/libheif/heif_properties.cc @@ -28,6 +28,7 @@ #include #include #include +#include int heif_item_get_properties_of_type(const struct heif_context* context, @@ -333,16 +334,11 @@ struct heif_error heif_item_add_raw_property(const struct heif_context* context, 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) +static struct heif_error find_property(const struct heif_context* context, + heif_item_id itemId, + heif_property_id propertyId, + std::shared_ptr *box_other) { - 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; @@ -356,7 +352,24 @@ struct heif_error heif_item_get_property_raw_size(const struct heif_context* con } auto box = properties[propertyId - 1]; - auto box_other = std::dynamic_pointer_cast(box); + *box_other = 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) { @@ -371,31 +384,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"}; @@ -408,3 +411,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_extended_type(const struct heif_context* context, + heif_item_id itemId, + heif_property_id propertyId, + uint8_t* extended_type) +{ + 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; +} \ No newline at end of file diff --git a/libheif/heif_properties.h b/libheif/api/libheif/heif_properties.h similarity index 89% rename from libheif/heif_properties.h rename to libheif/api/libheif/heif_properties.h index e5917785df..cfbe3d3b78 100644 --- a/libheif/heif_properties.h +++ b/libheif/api/libheif/heif_properties.h @@ -21,7 +21,7 @@ #ifndef LIBHEIF_HEIF_PROPERTIES_H #define LIBHEIF_HEIF_PROPERTIES_H -#include "libheif/heif.h" +#include "heif.h" #ifdef __cplusplus extern "C" { @@ -167,6 +167,24 @@ 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_extended_type(const struct heif_context* context, + heif_item_id itemId, + heif_property_id propertyId, + uint8_t* extended_type); #ifdef __cplusplus } #endif diff --git a/libheif/heif_regions.cc b/libheif/api/libheif/heif_regions.cc similarity index 99% rename from libheif/heif_regions.cc rename to libheif/api/libheif/heif_regions.cc index 54eb43f74f..2d26f2fb0d 100644 --- a/libheif/heif_regions.cc +++ b/libheif/api/libheif/heif_regions.cc @@ -19,9 +19,9 @@ * along with libheif. If not, see . */ -#include "libheif/heif_plugin.h" -#include "libheif/region.h" -#include "libheif/heif_regions.h" +#include "heif_plugin.h" +#include "region.h" +#include "heif_regions.h" #include "file.h" #include "api_structs.h" #include "context.h" diff --git a/libheif/heif_regions.h b/libheif/api/libheif/heif_regions.h similarity index 99% rename from libheif/heif_regions.h rename to libheif/api/libheif/heif_regions.h index 63083fba2a..4b6f662089 100644 --- a/libheif/heif_regions.h +++ b/libheif/api/libheif/heif_regions.h @@ -22,7 +22,7 @@ #ifndef LIBHEIF_HEIF_REGIONS_H #define LIBHEIF_HEIF_REGIONS_H -#include "libheif/heif.h" +#include "heif.h" #ifdef __cplusplus extern "C" { diff --git a/libheif/heif_version.h.in b/libheif/api/libheif/heif_version.h.in similarity index 100% rename from libheif/heif_version.h.in rename to libheif/api/libheif/heif_version.h.in diff --git a/libheif/bitstream.cc b/libheif/bitstream.cc index 80fcd02e68..fda64157ed 100644 --- a/libheif/bitstream.cc +++ b/libheif/bitstream.cc @@ -72,7 +72,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; } @@ -235,6 +235,31 @@ uint32_t BitstreamRange::read32() (buf[3])); } +uint64_t BitstreamRange::read64() +{ + if (!prepare_read(8)) { + return 0; + } + + uint8_t buf[8]; + + auto istr = get_istream(); + bool success = istr->read((char*) buf, 8); + + if (!success) { + set_eof_while_reading(); + return 0; + } + + return (uint64_t) (((uint64_t)buf[0] << 56) | + ((uint64_t)buf[1] << 48) | + ((uint64_t)buf[2] << 40) | + ((uint64_t)buf[3] << 32) | + ((uint64_t)buf[4] << 24) | + ((uint64_t)buf[5] << 16) | + ((uint64_t)buf[6] << 8) | + ((uint64_t)buf[7])); +} int32_t BitstreamRange::read32s() { diff --git a/libheif/bitstream.h b/libheif/bitstream.h index ed1de3d37b..a0188ef35e 100644 --- a/libheif/bitstream.h +++ b/libheif/bitstream.h @@ -150,6 +150,8 @@ class BitstreamRange int32_t read32s(); + uint64_t read64(); + std::string read_string(); bool read(uint8_t* data, size_t n); diff --git a/libheif/box.cc b/libheif/box.cc index 9f5c0d2b55..9b969bfa75 100644 --- a/libheif/box.cc +++ b/libheif/box.cc @@ -23,12 +23,12 @@ #include "box.h" #include "security_limits.h" #include "nclx.h" -#include "jpeg.h" -#include "jpeg2000.h" -#include "hevc.h" -#include "mask_image.h" -#include "vvc.h" -#include "avc.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 #include @@ -40,7 +40,11 @@ #include #if WITH_UNCOMPRESSED_CODEC -#include "uncompressed_box.h" +#include "codecs/uncompressed_box.h" +#endif + +#ifndef M_PI +#define M_PI 3.14159265358979323846 #endif @@ -410,12 +414,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()); } } @@ -539,6 +546,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; @@ -607,6 +626,14 @@ 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("icbr"): + box = std::make_shared(); + break; #endif // --- JPEG 2000 @@ -667,49 +694,57 @@ 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()); - } - - if (range.get_nesting_level() > MAX_BOX_NESTING_LEVEL) { return Error(heif_error_Memory_allocation_error, heif_suberror_Security_limit_exceeded, "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::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}; } @@ -904,13 +939,21 @@ Error Box_other::parse(BitstreamRange& range) 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 { @@ -947,7 +990,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"; @@ -1045,6 +1089,10 @@ Error Box_meta::parse(BitstreamRange& range) { 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,10 +1117,25 @@ std::string Box_meta::dump(Indent& indent) const } +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) { parse_full_box_header(range); + if (get_version() != 0) { + return unsupported_version_error("hdlr"); + } + m_pre_defined = range.read32(); m_handler_type = range.read32(); @@ -1121,6 +1184,11 @@ Error Box_pitm::parse(BitstreamRange& range) { parse_full_box_header(range); + if (get_version() > 1) { + return unsupported_version_error("pitm"); + } + + if (get_version() == 0) { m_item_ID = range.read16(); } @@ -1175,6 +1243,10 @@ Error Box_iloc::parse(BitstreamRange& range) { parse_full_box_header(range); + if (get_version() > 2) { + return unsupported_version_error("iloc"); + } + const int version = get_version(); uint16_t values4 = range.read16(); @@ -1704,6 +1776,11 @@ Error Box_infe::parse(BitstreamRange& range) { 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(); @@ -1851,6 +1928,11 @@ Error Box_iinf::parse(BitstreamRange& range) { 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; @@ -1962,6 +2044,10 @@ Error Box_pixi::parse(BitstreamRange& range) { 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); @@ -2269,6 +2355,10 @@ Error Box_ispe::parse(BitstreamRange& range) { parse_full_box_header(range); + if (get_version() != 0) { + return unsupported_version_error("ispe"); + } + m_image_width = range.read32(); m_image_height = range.read32(); @@ -2317,6 +2407,12 @@ Error Box_ipma::parse(BitstreamRange& range) { 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; @@ -2499,6 +2595,10 @@ Error Box_auxC::parse(BitstreamRange& range) { parse_full_box_header(range); + if (get_version() != 0) { + return unsupported_version_error("auxC"); + } + m_aux_type = range.read_string(); while (!range.eof()) { @@ -2786,6 +2886,10 @@ Error Box_iref::parse(BitstreamRange& range) { parse_full_box_header(range); + if (get_version() > 1) { + return unsupported_version_error("iref"); + } + while (!range.eof()) { Reference ref; @@ -3120,58 +3224,139 @@ Error Box_grpl::parse(BitstreamRange& range) { //parse_full_box_header(range); - //return read_children(range); + return read_children(range); // 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(); +} + + +Error Box_EntityToGroup::parse(BitstreamRange& range) +{ + 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()) { + break; } - group.group_id = range.read32(); - uint32_t nEntities = range.read32(); - for (uint32_t i = 0; i < nEntities; i++) { - if (range.eof()) { - break; - } + entity_ids.push_back(range.read32()); + } - group.entity_ids.push_back(range.read32()); + return Error::Ok; +} + +std::string Box_EntityToGroup::dump(Indent& indent) const +{ + std::ostringstream sstr; + sstr << Box::dump(indent); + + sstr << indent << "group id: " << group_id << "\n" + << indent << "entity IDs: "; + + bool first = true; + for (uint32_t id : entity_ids) { + if (first) { + first = false; + } + else { + sstr << ' '; } - m_entity_groups.push_back(group); + sstr << id; } - return range.get_error(); + sstr << "\n"; + + return sstr.str(); } -std::string Box_grpl::dump(Indent& indent) const +Error Box_ster::parse(BitstreamRange& range) +{ + Error err = Box_EntityToGroup::parse(range); + 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); - 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 << "left image ID: " << entity_ids[0] << "\n" + << indent << "right image ID: " << entity_ids[1] << "\n"; - for (uint32_t id : group.entity_ids) { - sstr << id << " "; - } + return sstr.str(); +} - sstr << "\n"; + + +Error Box_pymd::parse(BitstreamRange& range) +{ + Error err = Box_EntityToGroup::parse(range); + 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; +} + +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) { //parse_full_box_header(range); @@ -3194,6 +3379,10 @@ Error Box_dref::parse(BitstreamRange& range) { parse_full_box_header(range); + if (get_version() != 0) { + return unsupported_version_error("dref"); + } + uint32_t nEntities = range.read32(); /* @@ -3237,6 +3426,10 @@ Error Box_url::parse(BitstreamRange& range) { parse_full_box_header(range); + if (get_version() > 0) { + return unsupported_version_error("url"); + } + m_location = range.read_string(); return range.get_error(); @@ -3258,6 +3451,11 @@ std::string Box_url::dump(Indent& indent) const Error Box_udes::parse(BitstreamRange& range) { 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(); @@ -3343,8 +3541,12 @@ Error Box_cmin::parse(BitstreamRange& range) { parse_full_box_header(range); + if (get_version() > 0) { + return unsupported_version_error("cmin"); + } + m_denominatorShift = (get_flags() & 0x1F00) >> 8; - uint32_t denominator = (1 << m_denominatorShift); + uint32_t denominator = (1U << m_denominatorShift); m_matrix.focal_length_x = range.read32s() / (double)denominator; m_matrix.principal_point_x = range.read32s() / (double)denominator; @@ -3352,7 +3554,7 @@ Error Box_cmin::parse(BitstreamRange& range) if (get_flags() & 1) { m_skewDenominatorShift = ((get_flags()) & 0x1F0000) >> 16; - uint32_t skewDenominator = (1<(m_matrix.focal_length_x * denominator)); writer.write32s(static_cast(m_matrix.principal_point_x * denominator)); @@ -3432,7 +3634,7 @@ Error Box_cmin::write(StreamWriter& writer) const if (get_flags() & 1) { writer.write32s(static_cast(m_matrix.focal_length_y * denominator)); - uint32_t skewDenominator = (1 << m_skewDenominatorShift); + uint32_t skewDenominator = (1U << m_skewDenominatorShift); writer.write32s(static_cast(m_matrix.skew * skewDenominator)); } @@ -3526,6 +3728,10 @@ Error Box_cmex::parse(BitstreamRange& range) { parse_full_box_header(range); + if (get_version() > 0) { + return unsupported_version_error("cmex"); + } + m_matrix = ExtrinsicMatrix{}; if (get_flags() & pos_x_present) { @@ -3552,7 +3758,7 @@ Error Box_cmex::parse(BitstreamRange& range) int32_t quat_y = use32bit ? range.read32s() : range.read16s(); int32_t quat_z = use32bit ? range.read32s() : range.read16s(); - uint32_t div = 1<<(14 + (use32bit ? 16 : 0)); + uint32_t div = 1U << (14 + (use32bit ? 16 : 0)); m_matrix.rotation_as_quaternions = true; m_matrix.quaternion_x = quat_x / (double)div; diff --git a/libheif/box.h b/libheif/box.h index 3fcc8e3da2..ee70d904d0 100644 --- a/libheif/box.h +++ b/libheif/box.h @@ -22,7 +22,8 @@ #define LIBHEIF_BOX_H #include -#include "libheif/common_utils.h" +#include "common_utils.h" +#include "libheif/heif.h" #include "libheif/heif_properties.h" #include #include @@ -36,7 +37,6 @@ #include #include "error.h" -#include "heif.h" #include "logging.h" #include "bitstream.h" @@ -261,6 +261,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; @@ -867,19 +868,59 @@ class Box_grpl : public Box protected: Error parse(BitstreamRange& range) override; +}; - struct EntityGroup - { - FullBox header; - uint32_t group_id; - std::vector entity_ids; +class Box_EntityToGroup : public FullBox +{ +public: + std::string dump(Indent&) const override; + +protected: + uint32_t group_id; + std::vector entity_ids; + + Error parse(BitstreamRange& range) override; +}; + + +class Box_ster : public Box_EntityToGroup +{ +public: + 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]; } + +protected: + + Error parse(BitstreamRange& range) override; +}; + + +class Box_pymd : public Box_EntityToGroup +{ +public: + std::string dump(Indent&) const override; + +protected: + uint16_t tile_size_x; + uint16_t tile_size_y; + + 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; + std::vector m_layer_infos; + + Error parse(BitstreamRange& range) override; }; + + class Box_dinf : public Box { public: diff --git a/libheif/avc.cc b/libheif/codecs/avc.cc similarity index 99% rename from libheif/avc.cc rename to libheif/codecs/avc.cc index 47739c8e13..730613df10 100644 --- a/libheif/avc.cc +++ b/libheif/codecs/avc.cc @@ -19,7 +19,6 @@ */ #include "avc.h" -#include #include #include #include diff --git a/libheif/avc.h b/libheif/codecs/avc.h similarity index 99% rename from libheif/avc.h rename to libheif/codecs/avc.h index 595126442d..5f3c826a3a 100644 --- a/libheif/avc.h +++ b/libheif/codecs/avc.h @@ -25,6 +25,7 @@ #include "error.h" #include #include +#include class Box_avcC : public Box { public: diff --git a/libheif/avif.cc b/libheif/codecs/avif.cc similarity index 100% rename from libheif/avif.cc rename to libheif/codecs/avif.cc diff --git a/libheif/avif.h b/libheif/codecs/avif.h similarity index 99% rename from libheif/avif.h rename to libheif/codecs/avif.h index 6924ac2315..7805ff5c54 100644 --- a/libheif/avif.h +++ b/libheif/codecs/avif.h @@ -27,7 +27,7 @@ #include #include -#include "heif.h" +#include "libheif/heif.h" #include "box.h" #include "error.h" diff --git a/libheif/hevc.cc b/libheif/codecs/hevc.cc similarity index 97% rename from libheif/hevc.cc rename to libheif/codecs/hevc.cc index 4c23510a22..aea9ef65ad 100644 --- a/libheif/hevc.cc +++ b/libheif/codecs/hevc.cc @@ -427,6 +427,12 @@ 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()); + if (reader.get_bits_remaining() < 32) { + return {heif_error_Invalid_input, + heif_suberror_End_of_data, + "HEVC SEI NAL too short"}; + } + uint32_t len = (uint32_t) reader.get_bits(32); if (len > data.size() - 4) { @@ -435,8 +441,15 @@ 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); + 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 = (uint32_t) sei_reader.get_bits(32); (void) nal_size; @@ -448,6 +461,12 @@ 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)); diff --git a/libheif/hevc.h b/libheif/codecs/hevc.h similarity index 99% rename from libheif/hevc.h rename to libheif/codecs/hevc.h index af5b285c06..3d2a0c3e88 100644 --- a/libheif/hevc.h +++ b/libheif/codecs/hevc.h @@ -21,7 +21,7 @@ #ifndef HEIF_HEVC_H #define HEIF_HEVC_H -#include "heif.h" +#include "libheif/heif.h" #include "box.h" #include "error.h" diff --git a/libheif/jpeg.cc b/libheif/codecs/jpeg.cc similarity index 100% rename from libheif/jpeg.cc rename to libheif/codecs/jpeg.cc diff --git a/libheif/jpeg.h b/libheif/codecs/jpeg.h similarity index 100% rename from libheif/jpeg.h rename to libheif/codecs/jpeg.h diff --git a/libheif/jpeg2000.cc b/libheif/codecs/jpeg2000.cc similarity index 100% rename from libheif/jpeg2000.cc rename to libheif/codecs/jpeg2000.cc diff --git a/libheif/jpeg2000.h b/libheif/codecs/jpeg2000.h similarity index 97% rename from libheif/jpeg2000.h rename to libheif/codecs/jpeg2000.h index e21992041a..42ff72c669 100644 --- a/libheif/jpeg2000.h +++ b/libheif/codecs/jpeg2000.h @@ -324,11 +324,9 @@ 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, @@ -405,7 +403,7 @@ class JPEG2000_Extension_Capability { private: const uint8_t ident; - uint16_t value; + uint16_t value = 0; }; class JPEG2000_Extension_Capability_HT : public JPEG2000_Extension_Capability @@ -438,7 +436,7 @@ class JPEG2000_CAP_segment struct JPEG2000MainHeader { public: - JPEG2000MainHeader() {} + JPEG2000MainHeader() = default; Error parseHeader(const HeifFile& file, const heif_item_id imageID); @@ -499,7 +497,7 @@ struct JPEG2000MainHeader uint16_t read16() { - uint16_t res = (headerData[cursor] << 8) | headerData[cursor + 1]; + uint16_t res = (uint16_t)((headerData[cursor] << 8) | headerData[cursor + 1]); cursor += 2; return res; } diff --git a/libheif/mask_image.cc b/libheif/codecs/mask_image.cc similarity index 86% rename from libheif/mask_image.cc rename to libheif/codecs/mask_image.cc index 7abdb10b1b..4d8e57d5f6 100644 --- a/libheif/mask_image.cc +++ b/libheif/codecs/mask_image.cc @@ -25,10 +25,10 @@ #include #include #include -#include +#include #include "libheif/heif.h" -#include "libheif/logging.h" +#include "logging.h" #include "mask_image.h" Error Box_mskC::parse(BitstreamRange& range) @@ -55,15 +55,13 @@ 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); + Error error = context->get_heif_file()->get_properties(ID, item_properties); if (error) { return error; } @@ -76,16 +74,11 @@ Error MaskImageCodec::decode_mask_image(const std::shared_ptr& h 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()); + error = context->check_resolution(width, height); + if (error) { + return error; } + found_ispe = true; } diff --git a/libheif/mask_image.h b/libheif/codecs/mask_image.h similarity index 91% rename from libheif/mask_image.h rename to libheif/codecs/mask_image.h index a05842128d..b8e67c2e80 100644 --- a/libheif/mask_image.h +++ b/libheif/codecs/mask_image.h @@ -70,11 +70,9 @@ 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, @@ -84,4 +82,3 @@ class MaskImageCodec }; #endif //LIBHEIF_MASK_IMAGE_H - diff --git a/libheif/uncompressed.h b/libheif/codecs/uncompressed.h similarity index 100% rename from libheif/uncompressed.h rename to libheif/codecs/uncompressed.h diff --git a/libheif/codecs/uncompressed_box.cc b/libheif/codecs/uncompressed_box.cc new file mode 100644 index 0000000000..ba25342dc8 --- /dev/null +++ b/libheif/codecs/uncompressed_box.cc @@ -0,0 +1,464 @@ +/* + * 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 "libheif/heif.h" +#include "security_limits.h" +#include "uncompressed.h" +#include "uncompressed_box.h" + + +/** + * Check for valid component format. + * + * @param format the format value to check + * @return true if the format is a valid value, or false otherwise + */ +bool is_valid_component_format(uint8_t format) +{ + return format <= component_format_max_valid; +} + +static std::map sNames_uncompressed_component_format{ + {component_format_unsigned, "unsigned"}, + {component_format_float, "float"}, + {component_format_complex, "complex"} +}; + + +/** + * Check for valid interleave mode. + * + * @param interleave the interleave value to check + * @return true if the interleave mode is valid, or false otherwise + */ +bool is_valid_interleave_mode(uint8_t interleave) +{ + return interleave <= interleave_mode_max_valid; +} + +static std::map sNames_uncompressed_interleave_mode{ + {interleave_mode_component, "component"}, + {interleave_mode_pixel, "pixel"}, + {interleave_mode_mixed, "mixed"}, + {interleave_mode_row, "row"}, + {interleave_mode_tile_component, "tile-component"}, + {interleave_mode_multi_y, "multi-y"} +}; + + +/** + * Check for valid sampling mode. + * + * @param sampling the sampling value to check + * @return true if the sampling mode is valid, or false otherwise + */ +bool is_valid_sampling_mode(uint8_t sampling) +{ + return sampling <= sampling_mode_max_valid; +} + +static std::map sNames_uncompressed_sampling_mode{ + {sampling_mode_no_subsampling, "no subsampling"}, + {sampling_mode_422, "4:2:2"}, + {sampling_mode_420, "4:2:0"}, + {sampling_mode_411, "4:1:1"} +}; + + +bool is_predefined_component_type(uint16_t type) +{ + // check whether the component type can be mapped to heif_uncompressed_component_type and we have a name defined for + // it in sNames_uncompressed_component_type. + return (type >= 0 && type <= component_type_max_valid); +} + +static std::map sNames_uncompressed_component_type{ + {component_type_monochrome, "monochrome"}, + {component_type_Y, "Y"}, + {component_type_Cb, "Cb"}, + {component_type_Cr, "Cr"}, + {component_type_red, "red"}, + {component_type_green, "green"}, + {component_type_blue, "blue"}, + {component_type_alpha, "alpha"}, + {component_type_depth, "depth"}, + {component_type_disparity, "disparity"}, + {component_type_palette, "palette"}, + {component_type_filter_array, "filter-array"}, + {component_type_padded, "padded"}, + {component_type_cyan, "cyan"}, + {component_type_magenta, "magenta"}, + {component_type_yellow, "yellow"}, + {component_type_key_black, "key (black)"} +}; + +template const char* get_name(T val, const std::map& table) +{ + auto iter = table.find(val); + if (iter == table.end()) { + return "unknown"; + } + else { + return iter->second; + } +} + +Error Box_cmpd::parse(BitstreamRange& range) +{ + unsigned int component_count = range.read32(); + + for (unsigned int i = 0; i < component_count && !range.error() && !range.eof(); i++) { + Component component; + component.component_type = range.read16(); + if (component.component_type >= 0x8000) { + component.component_type_uri = range.read_string(); + } + else { + component.component_type_uri = std::string(); + } + m_components.push_back(component); + } + + return range.get_error(); +} + +std::string Box_cmpd::Component::get_component_type_name(uint16_t component_type) +{ + std::stringstream sstr; + + if (is_predefined_component_type(component_type)) { + sstr << get_name(heif_uncompressed_component_type(component_type), sNames_uncompressed_component_type) << "\n"; + } + else { + sstr << "0x" << std::hex << component_type << std::dec << "\n"; + } + + return sstr.str(); +} + + +std::string Box_cmpd::dump(Indent& indent) const +{ + std::ostringstream sstr; + sstr << Box::dump(indent); + + for (const auto& component : m_components) { + sstr << indent << "component_type: " << component.get_component_type_name(); + + if (component.component_type >= 0x8000) { + sstr << indent << "| component_type_uri: " << component.component_type_uri << "\n"; + } + } + + return sstr.str(); +} + +Error Box_cmpd::write(StreamWriter& writer) const +{ + size_t box_start = reserve_box_header_space(writer); + + writer.write32((uint32_t) m_components.size()); + for (const auto& component : m_components) { + writer.write16(component.component_type); + if (component.component_type >= 0x8000) { + writer.write(component.component_type_uri); + } + } + + prepend_header(writer, box_start); + + return Error::Ok; +} + +Error Box_uncC::parse(BitstreamRange& range) +{ + parse_full_box_header(range); + m_profile = range.read32(); + if (get_version() == 1) { + if (m_profile == fourcc_to_uint32("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"))) { + 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); + Box_uncC::Component component3 = {3, 8, component_format_unsigned, 0}; + add_component(component3); + } else { + return Error{heif_error_Invalid_input, heif_suberror_Invalid_parameter_value, "Invalid component format"}; + } + } else if (get_version() == 0) { + + unsigned int component_count = range.read32(); + + for (unsigned int i = 0; i < component_count && !range.error() && !range.eof(); i++) { + Component component; + component.component_index = range.read16(); + component.component_bit_depth = uint16_t(range.read8() + 1); + component.component_format = range.read8(); + component.component_align_size = range.read8(); + m_components.push_back(component); + + if (!is_valid_component_format(component.component_format)) { + return Error{heif_error_Invalid_input, heif_suberror_Invalid_parameter_value, "Invalid component format"}; + } + } + + m_sampling_type = range.read8(); + if (!is_valid_sampling_mode(m_sampling_type)) { + return Error{heif_error_Invalid_input, heif_suberror_Invalid_parameter_value, "Invalid sampling mode"}; + } + + m_interleave_type = range.read8(); + if (!is_valid_interleave_mode(m_interleave_type)) { + return Error{heif_error_Invalid_input, heif_suberror_Invalid_parameter_value, "Invalid interleave mode"}; + } + + m_block_size = range.read8(); + + uint8_t flags = range.read8(); + m_components_little_endian = !!(flags & 0x80); + m_block_pad_lsb = !!(flags & 0x40); + m_block_little_endian = !!(flags & 0x20); + m_block_reversed = !!(flags & 0x10); + m_pad_unknown = !!(flags & 0x08); + + m_pixel_size = range.read32(); + + m_row_align_size = range.read32(); + + m_tile_align_size = range.read32(); + + 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 = num_tile_rows_minus_one + 1; + } + return range.get_error(); +} + + + +std::string Box_uncC::dump(Indent& indent) const +{ + std::ostringstream sstr; + sstr << Box::dump(indent); + + sstr << indent << "profile: " << m_profile; + if (m_profile != 0) { + sstr << " (" << to_fourcc(m_profile) << ")"; + } + sstr << "\n"; + if (get_version() == 0) { + for (const auto& component : m_components) { + sstr << indent << "component_index: " << component.component_index << "\n"; + 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"; + } + + sstr << indent << "sampling_type: " << get_name(heif_uncompressed_sampling_mode(m_sampling_type), sNames_uncompressed_sampling_mode) << "\n"; + + sstr << indent << "interleave_type: " << get_name(heif_uncompressed_interleave_mode(m_interleave_type), sNames_uncompressed_interleave_mode) << "\n"; + + sstr << indent << "block_size: " << (int) m_block_size << "\n"; + + sstr << indent << "components_little_endian: " << m_components_little_endian << "\n"; + sstr << indent << "block_pad_lsb: " << m_block_pad_lsb << "\n"; + sstr << indent << "block_little_endian: " << m_block_little_endian << "\n"; + sstr << indent << "block_reversed: " << m_block_reversed << "\n"; + sstr << indent << "pad_unknown: " << m_pad_unknown << "\n"; + + sstr << indent << "pixel_size: " << m_pixel_size << "\n"; + + sstr << indent << "row_align_size: " << m_row_align_size << "\n"; + + sstr << indent << "tile_align_size: " << m_tile_align_size << "\n"; + + sstr << indent << "num_tile_cols: " << m_num_tile_cols << "\n"; + + sstr << indent << "num_tile_rows: " << m_num_tile_rows << "\n"; + } + return sstr.str(); +} + +Error Box_uncC::write(StreamWriter& writer) const +{ + size_t box_start = reserve_box_header_space(writer); + writer.write32(m_profile); + if (get_version() == 1) { + } + else if (get_version() == 0) { + writer.write32((uint32_t)m_components.size()); + for (const auto &component : m_components) { + if (component.component_bit_depth < 1 || component.component_bit_depth > 256) { + return {heif_error_Invalid_input, heif_suberror_Invalid_parameter_value, "component bit-depth out of range [1..256]"}; + } + + writer.write16(component.component_index); + writer.write8(uint8_t(component.component_bit_depth - 1)); + writer.write8(component.component_format); + writer.write8(component.component_align_size); + } + writer.write8(m_sampling_type); + writer.write8(m_interleave_type); + writer.write8(m_block_size); + uint8_t flags = 0; + flags |= (m_components_little_endian ? 0x80 : 0); + flags |= (m_block_pad_lsb ? 0x40 : 0); + flags |= (m_block_little_endian ? 0x20 : 0); + flags |= (m_block_reversed ? 0x10 : 0); + flags |= (m_pad_unknown ? 0x08 : 0); + writer.write8(flags); + writer.write32(m_pixel_size); + writer.write32(m_row_align_size); + writer.write32(m_tile_align_size); + writer.write32(m_num_tile_cols - 1); + writer.write32(m_num_tile_rows - 1); + } + prepend_header(writer, box_start); + + return Error::Ok; +} + + +Error Box_cmpC::parse(BitstreamRange& range) +{ + parse_full_box_header(range); + + if (get_version() != 0) { + return unsupported_version_error("cmpC"); + } + + compression_type = range.read32(); + uint8_t v = range.read8(); + must_decompress_individual_entities = ((v & 0x80) == 0x80); + compressed_range_type = (v & 0x7f); + return range.get_error(); +} + + +std::string Box_cmpC::dump(Indent& indent) const +{ + std::ostringstream sstr; + sstr << Box::dump(indent); + sstr << indent << "compression_type: " << to_fourcc(compression_type) << "\n"; + sstr << indent << "must_decompress_individual_entities: " << must_decompress_individual_entities << "\n"; + sstr << indent << "compressed_entity_type: " << (int)compressed_range_type << "\n"; + return sstr.str(); +} + +Error Box_cmpC::write(StreamWriter& writer) const +{ + size_t box_start = reserve_box_header_space(writer); + + writer.write32(compression_type); + uint8_t v = must_decompress_individual_entities ? 0x80 : 0x00; + v |= (compressed_range_type & 0x7F); + writer.write8(v); + + prepend_header(writer, box_start); + + return Error::Ok; +} + + +Error Box_icbr::parse(BitstreamRange& range) +{ + parse_full_box_header(range); + + if ((get_version() != 0) && (get_version() != 1)) { + return unsupported_version_error("icbr"); + } + + uint32_t num_ranges = range.read32(); + for (uint32_t r = 0; r < num_ranges; r++) { + struct ByteRange byteRange; + if (get_version() == 1) { + byteRange.range_offset = range.read64(); + byteRange.range_size = range.read64(); + } else if (get_version() == 0) { + byteRange.range_offset = range.read32(); + byteRange.range_size = range.read32(); + } else { + return Error(heif_error_Usage_error, heif_suberror_Unsupported_parameter, "Unsupported icbr version"); + } + if (range.get_error() != Error::Ok) { + return range.get_error(); + } + m_ranges.push_back(byteRange); + } + return range.get_error(); +} + + +std::string Box_icbr::dump(Indent& indent) const +{ + std::ostringstream sstr; + sstr << Box::dump(indent); + sstr << indent << "num_ranges: " << m_ranges.size() << "\n"; + for (ByteRange range: m_ranges) { + sstr << indent << "range_offset: " << range.range_offset << ", range_size: " << range.range_size << "\n"; + } + return sstr.str(); +} + +Error Box_icbr::write(StreamWriter& writer) const +{ + size_t box_start = reserve_box_header_space(writer); + + writer.write32((uint32_t)m_ranges.size()); + for (ByteRange range: m_ranges) { + if (get_version() == 1) { + writer.write64(range.range_offset); + writer.write64(range.range_size); + } else if (get_version() == 0) { + writer.write32((uint32_t)range.range_offset); + writer.write32((uint32_t)range.range_size); + } + } + prepend_header(writer, box_start); + + return Error::Ok; +} diff --git a/libheif/uncompressed_box.h b/libheif/codecs/uncompressed_box.h similarity index 70% rename from libheif/uncompressed_box.h rename to libheif/codecs/uncompressed_box.h index e48000c9a0..e6a7d17751 100644 --- a/libheif/uncompressed_box.h +++ b/libheif/codecs/uncompressed_box.h @@ -24,7 +24,7 @@ #include "box.h" #include "bitstream.h" -#include "libheif/uncompressed.h" +#include "uncompressed.h" #include #include @@ -76,25 +76,12 @@ class Box_cmpd : public Box class Box_uncC : public FullBox { public: - Box_uncC() : - m_profile(0), - m_sampling_type(sampling_mode_no_subsampling), - m_interleave_type(interleave_mode_component), - m_block_size(0), - m_components_little_endian(false), - m_block_pad_lsb(false), - m_block_little_endian(false), - m_block_reversed(false), - m_pad_unknown(false), - m_pixel_size(0), - m_row_align_size(0), - m_tile_align_size(0), - m_num_tile_cols(1), - m_num_tile_rows(1) - { + Box_uncC() { set_short_type(fourcc("uncC")); } + void derive_box_version() override {}; + std::string dump(Indent&) const override; Error write(StreamWriter& writer) const override; @@ -215,22 +202,87 @@ class Box_uncC : public FullBox protected: Error parse(BitstreamRange& range) override; - uint32_t m_profile; + uint32_t m_profile = 0; // not compliant to any profile std::vector m_components; - uint8_t m_sampling_type; - uint8_t m_interleave_type; - uint8_t m_block_size; - bool m_components_little_endian; - bool m_block_pad_lsb; - bool m_block_little_endian; - bool m_block_reversed; - bool m_pad_unknown; - uint32_t m_pixel_size; - uint32_t m_row_align_size; - uint32_t m_tile_align_size; - uint32_t m_num_tile_cols; - uint32_t m_num_tile_rows; + uint8_t m_sampling_type = sampling_mode_no_subsampling; // no subsampling + uint8_t m_interleave_type = interleave_mode_pixel; // component interleaving + uint8_t m_block_size = 0; + bool m_components_little_endian = false; + bool m_block_pad_lsb = false; + bool m_block_little_endian = false; + bool m_block_reversed = false; + bool m_pad_unknown = false; + uint32_t m_pixel_size = 0; + uint32_t m_row_align_size = 0; + uint32_t m_tile_align_size = 0; + uint32_t m_num_tile_cols = 1; + uint32_t m_num_tile_rows = 1; +}; + +/** + * Generic compression 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 compression_type; } + bool get_must_decompress_individual_entities() const { return must_decompress_individual_entities; } + uint8_t get_compressed_range_type() const { return compressed_range_type; } + + Error write(StreamWriter& writer) const override; + +protected: + Error parse(BitstreamRange& range) override; + + uint32_t compression_type; + bool must_decompress_individual_entities; + uint8_t compressed_range_type; +}; + +/** + * Item compressed byte range info (icbr). + * + * This is from ISO/IEC 23001-17 Amd 2. + */ +class Box_icbr : public FullBox +{ +public: + Box_icbr() + { + set_short_type(fourcc("icbr")); + } + + struct ByteRange + { + uint64_t range_offset; + uint64_t range_size; + }; + + const std::vector& get_ranges() const { return m_ranges; } + + void add_component(const ByteRange& range) + { + m_ranges.push_back(range); + } + + std::string dump(Indent&) const override; + + Error write(StreamWriter& writer) const override; + +protected: + Error parse(BitstreamRange& range) override; + + std::vector m_ranges; }; #endif //LIBHEIF_UNCOMPRESSED_BOX_H diff --git a/libheif/uncompressed_image.cc b/libheif/codecs/uncompressed_image.cc similarity index 90% rename from libheif/uncompressed_image.cc rename to libheif/codecs/uncompressed_image.cc index 51053743c8..a1fcf7ca11 100644 --- a/libheif/uncompressed_image.cc +++ b/libheif/codecs/uncompressed_image.cc @@ -27,15 +27,32 @@ #include #include -#include "libheif/common_utils.h" -#include "libheif/error.h" +#include "common_utils.h" +#include "context.h" +#include "compression.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; @@ -228,6 +245,12 @@ static Error get_heif_chroma_uncompressed(std::shared_ptr& uncC, std:: *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; @@ -303,9 +326,16 @@ int UncompressedImageCodec::get_luma_bits_per_pixel_from_configuration_unci(cons 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) { + if (!uncC_box) { return -1; } + if (!cmpd_box) { + if (isKnownUncompressedFrameConfigurationBoxProfile(uncC_box)) { + return 8; + } else { + return -1; + } + } int luma_bits = 0; int alternate_channel_bits = 0; @@ -339,9 +369,56 @@ int UncompressedImageCodec::get_luma_bits_per_pixel_from_configuration_unci(cons } } -static bool map_uncompressed_component_to_channel(const std::shared_ptr &cmpd, const Box_uncC::Component component, heif_channel *channel) { +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; @@ -375,8 +452,8 @@ static bool map_uncompressed_component_to_channel(const std::shared_ptr& data) : BitReader(data.data(), (int)data.size()) {} @@ -468,7 +545,7 @@ class AbstractDecoder return dst_row_number * dst_plane_stride; } - heif_channel channel; + heif_channel channel = heif_channel_Y; uint8_t* dst_plane; uint8_t* other_chroma_dst_plane; int dst_plane_stride; @@ -484,7 +561,7 @@ class AbstractDecoder std::vector channelList; - void buildChannelList(std::shared_ptr& img) { + void buildChannelList(std::shared_ptr& img) { for (Box_uncC::Component component : m_uncC->get_components()) { ChannelListEntry entry = buildChannelListEntry(component, img); channelList.push_back(entry); @@ -519,7 +596,7 @@ class AbstractDecoder private: ChannelListEntry buildChannelListEntry(Box_uncC::Component component, std::shared_ptr &img) { ChannelListEntry entry; - entry.use_channel = map_uncompressed_component_to_channel(m_cmpd, component, &(entry.channel)); + 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; @@ -782,14 +859,12 @@ static AbstractDecoder* makeDecoder(uint32_t width, uint32_t height, const std:: } } -Error UncompressedImageCodec::decode_uncompressed_image(const std::shared_ptr& heif_file, +Error UncompressedImageCodec::decode_uncompressed_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) + const std::vector& source_data) { - if (uncompressed_data.empty()) { + if (source_data.empty()) { return {heif_error_Invalid_input, heif_suberror_Unspecified, "Uncompressed image data is empty"}; @@ -798,7 +873,7 @@ Error UncompressedImageCodec::decode_uncompressed_image(const std::shared_ptr> item_properties; - Error error = heif_file->get_properties(ID, item_properties); + Error error = context->get_heif_file()->get_properties(ID, item_properties); if (error) { printf("failed to get properties\n"); return error; @@ -809,21 +884,19 @@ Error UncompressedImageCodec::decode_uncompressed_image(const std::shared_ptr cmpd; std::shared_ptr uncC; + std::shared_ptr cmpC; + std::shared_ptr icbr; + 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()); + error = context->check_resolution(width, height); + if (error) { + return error; } + found_ispe = true; } @@ -836,18 +909,36 @@ Error UncompressedImageCodec::decode_uncompressed_image(const std::shared_ptr(prop); + if (maybe_cmpC) { + cmpC = maybe_cmpC; + } + + auto maybe_icbr = std::dynamic_pointer_cast(prop); + if (maybe_icbr) { + icbr = maybe_icbr; + } + } // if we miss a required box, show error - - if (!found_ispe || !cmpd || !uncC) { - printf("failed to get required boxes\n"); + if (!found_ispe) { return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_data_version, - "Missing required box for uncompressed codec"); + "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 @@ -871,7 +962,7 @@ Error UncompressedImageCodec::decode_uncompressed_image(const std::shared_ptrget_components()) { heif_channel channel; - if (map_uncompressed_component_to_channel(cmpd, component, &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 { @@ -882,7 +973,7 @@ Error UncompressedImageCodec::decode_uncompressed_image(const std::shared_ptrdecode(uncompressed_data, img); + Error result = decoder->decode(source_data, img); delete decoder; return result; } else { @@ -911,13 +1002,13 @@ Error fill_cmpd_and_uncC(std::shared_ptr& cmpd, std::shared_ptradd_component(cbComponent); Box_cmpd::Component crComponent = {component_type_Cr}; cmpd->add_component(crComponent); - u_int8_t bpp_y = image->get_bits_per_pixel(heif_channel_Y); + 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); - u_int8_t bpp_cb = image->get_bits_per_pixel(heif_channel_Cb); + 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); - u_int8_t bpp_cr = image->get_bits_per_pixel(heif_channel_Cr); + 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) @@ -1089,22 +1180,48 @@ Error fill_cmpd_and_uncC(std::shared_ptr& cmpd, 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 cmpd = std::make_shared(); std::shared_ptr uncC = std::make_shared(); - Error error = fill_cmpd_and_uncC(cmpd, uncC, src_image); - if (error) - { - return error; + if (options.prefer_uncC_short_form) { + maybe_make_minimised_uncC(uncC, src_image); } - heif_file->add_property(out_image->get_id(), cmpd, true); - heif_file->add_property(out_image->get_id(), uncC, true); + 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) { diff --git a/libheif/uncompressed_image.h b/libheif/codecs/uncompressed_image.h similarity index 88% rename from libheif/uncompressed_image.h rename to libheif/codecs/uncompressed_image.h index 742f9a76f8..755fac7c11 100644 --- a/libheif/uncompressed_image.h +++ b/libheif/codecs/uncompressed_image.h @@ -31,17 +31,16 @@ #include #include +class HeifContext; 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, + static Error decode_uncompressed_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_uncompressed_image(const std::shared_ptr& heif_file, diff --git a/libheif/vvc.cc b/libheif/codecs/vvc.cc similarity index 87% rename from libheif/vvc.cc rename to libheif/codecs/vvc.cc index 9bdb79aef4..a0853667f4 100644 --- a/libheif/vvc.cc +++ b/libheif/codecs/vvc.cc @@ -23,6 +23,7 @@ #include #include #include +#include Error Box_vvcC::parse(BitstreamRange& range) { @@ -135,10 +136,10 @@ Error Box_vvcC::write(StreamWriter& writer) const assert(c.lengthSize == 1 || c.lengthSize == 2 || c.lengthSize == 4); - uint8_t v = ((c.constantFrameRate << 6) | - (c.numTemporalLayers << 3) | - ((c.lengthSize - 1) << 1) | - (c.ptl_present_flag ? 1 : 0)); + 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) { @@ -156,7 +157,7 @@ Error Box_vvcC::write(StreamWriter& writer) const } if (c.bit_depth_present_flag) { - v |= 0x10 | ((c.bit_depth - 8) << 1); + v |= (uint8_t)(0x10 | ((c.bit_depth - 8) << 1)); } else { v |= 0x0e; @@ -169,15 +170,28 @@ Error Box_vvcC::write(StreamWriter& writer) const // TODO: error } - writer.write8((int)m_nal_array.size()); + 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); - writer.write16(nal_array.m_nal_units.size()); + 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) { - writer.write16(nal.size()); + + 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); } } @@ -282,8 +296,8 @@ Error parse_sps_for_vvcC_configuration(const uint8_t* sps, size_t size, // skip VPS ID reader.skip_bits(4); - config->numTemporalLayers = reader.get_bits(3) + 1; - config->chroma_format_idc = reader.get_bits(2); + 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); @@ -343,6 +357,7 @@ Error parse_sps_for_vvcC_configuration(const uint8_t* sps, size_t size, 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; @@ -351,7 +366,11 @@ Error parse_sps_for_vvcC_configuration(const uint8_t* sps, size_t size, int sps_conformance_window_flag = reader.get_bits(1); if (sps_conformance_window_flag) { - assert(false); // TODO + 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); @@ -363,7 +382,11 @@ Error parse_sps_for_vvcC_configuration(const uint8_t* sps, size_t size, success = reader.get_uvlc(&bitDepth_minus8); (void)success; - config->bit_depth = bitDepth_minus8 + 8; + 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/vvc.h b/libheif/codecs/vvc.h similarity index 100% rename from libheif/vvc.h rename to libheif/codecs/vvc.h diff --git a/libheif/color-conversion/chroma_sampling.h b/libheif/color-conversion/chroma_sampling.h index 441f88e3ce..446bd952f8 100644 --- a/libheif/color-conversion/chroma_sampling.h +++ b/libheif/color-conversion/chroma_sampling.h @@ -21,7 +21,7 @@ #ifndef LIBHEIF_CHROMA_SAMPLING_H #define LIBHEIF_CHROMA_SAMPLING_H -#include "libheif/color-conversion/colorconversion.h" +#include "color-conversion/colorconversion.h" #include #include diff --git a/libheif/color-conversion/colorconversion.cc b/libheif/color-conversion/colorconversion.cc index 5a0d6b57a4..412e0b8df7 100644 --- a/libheif/color-conversion/colorconversion.cc +++ b/libheif/color-conversion/colorconversion.cc @@ -20,8 +20,8 @@ #include "colorconversion.h" -#include "libheif/common_utils.h" -#include "libheif/nclx.h" +#include "common_utils.h" +#include "nclx.h" #include #include #include diff --git a/libheif/color-conversion/colorconversion.h b/libheif/color-conversion/colorconversion.h index bcd4940aa4..969093e16d 100644 --- a/libheif/color-conversion/colorconversion.h +++ b/libheif/color-conversion/colorconversion.h @@ -21,7 +21,7 @@ #ifndef LIBHEIF_COLORCONVERSION_H #define LIBHEIF_COLORCONVERSION_H -#include "libheif/pixelimage.h" +#include "pixelimage.h" #include #include #include diff --git a/libheif/color-conversion/rgb2yuv.cc b/libheif/color-conversion/rgb2yuv.cc index 1c4104a438..0be8042e51 100644 --- a/libheif/color-conversion/rgb2yuv.cc +++ b/libheif/color-conversion/rgb2yuv.cc @@ -23,8 +23,8 @@ #include #include #include "rgb2yuv.h" -#include "libheif/nclx.h" -#include "libheif/common_utils.h" +#include "nclx.h" +#include "common_utils.h" template diff --git a/libheif/color-conversion/rgb2yuv.h b/libheif/color-conversion/rgb2yuv.h index 1f2fdadd52..74b929b0a0 100644 --- a/libheif/color-conversion/rgb2yuv.h +++ b/libheif/color-conversion/rgb2yuv.h @@ -21,7 +21,7 @@ #ifndef LIBHEIF_COLORCONVERSION_RGB2YUV_H #define LIBHEIF_COLORCONVERSION_RGB2YUV_H -#include "libheif/color-conversion/colorconversion.h" +#include "color-conversion/colorconversion.h" #include #include diff --git a/libheif/color-conversion/rgb2yuv_sharp.cc b/libheif/color-conversion/rgb2yuv_sharp.cc index b7994f62e4..8423391c9f 100644 --- a/libheif/color-conversion/rgb2yuv_sharp.cc +++ b/libheif/color-conversion/rgb2yuv_sharp.cc @@ -27,8 +27,8 @@ #include #include -#include "libheif/nclx.h" -#include "libheif/common_utils.h" +#include "nclx.h" +#include "common_utils.h" static inline bool PlatformIsBigEndian() { diff --git a/libheif/color-conversion/yuv2rgb.cc b/libheif/color-conversion/yuv2rgb.cc index 473763ad37..06f7ae756c 100644 --- a/libheif/color-conversion/yuv2rgb.cc +++ b/libheif/color-conversion/yuv2rgb.cc @@ -21,8 +21,8 @@ #include #include #include "yuv2rgb.h" -#include "libheif/nclx.h" -#include "libheif/common_utils.h" +#include "nclx.h" +#include "common_utils.h" template diff --git a/libheif/common_utils.h b/libheif/common_utils.h index d0913f5d41..f7f4a5f5ab 100644 --- a/libheif/common_utils.h +++ b/libheif/common_utils.h @@ -22,7 +22,7 @@ #define LIBHEIF_COMMON_UTILS_H #include -#include +#include "libheif/heif.h" #ifdef _MSC_VER #define MAYBE_UNUSED @@ -33,10 +33,10 @@ constexpr inline uint32_t fourcc_to_uint32(const char* id) { - return ((((uint32_t) id[0]) << 24) | - (((uint32_t) id[1]) << 16) | - (((uint32_t) id[2]) << 8) | - (((uint32_t) id[3]) << 0)); + return (((((uint32_t) id[0])&0xFF) << 24) | + ((((uint32_t) id[1])&0xFF) << 16) | + ((((uint32_t) id[2])&0xFF) << 8) | + ((((uint32_t) id[3])&0xFF) << 0)); } diff --git a/libheif/compression.h b/libheif/compression.h new file mode 100644 index 0000000000..16490241ec --- /dev/null +++ b/libheif/compression.h @@ -0,0 +1,96 @@ +/* + * 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); + +#endif + +#endif //LIBHEIF_COMPRESSION_H diff --git a/libheif/compression_brotli.cc b/libheif/compression_brotli.cc new file mode 100644 index 0000000000..198e2562a5 --- /dev/null +++ b/libheif/compression_brotli.cc @@ -0,0 +1,85 @@ +/* + * 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 "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; +} + +#endif diff --git a/libheif/metadata_compression.cc b/libheif/compression_zlib.cc similarity index 64% rename from libheif/metadata_compression.cc rename to libheif/compression_zlib.cc index b738d1f62d..8efab59a0b 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,39 @@ 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 {}; + 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 6000dc84e7..d95c029e93 100644 --- a/libheif/context.cc +++ b/libheif/context.cc @@ -18,10 +18,10 @@ * along with libheif. If not, see . */ -#include "libheif/box.h" -#include "libheif/error.h" +#include "box.h" +#include "error.h" #include "libheif/heif.h" -#include "libheif/region.h" +#include "region.h" #include #include #include @@ -38,20 +38,20 @@ #include "context.h" #include "file.h" #include "pixelimage.h" -#include "api_structs.h" +#include "libheif/api_structs.h" #include "security_limits.h" -#include "hevc.h" -#include "vvc.h" -#include "avif.h" -#include "jpeg.h" +#include "compression.h" +#include "color-conversion/colorconversion.h" #include "plugin_registry.h" -#include "libheif/color-conversion/colorconversion.h" -#include "mask_image.h" -#include "metadata_compression.h" -#include "jpeg2000.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" #if WITH_UNCOMPRESSED_CODEC -#include "uncompressed_image.h" +#include "codecs/uncompressed_image.h" #endif @@ -178,7 +178,13 @@ Error ImageGrid::parse(const std::vector& data) } uint8_t version = data[0]; - (void) version; // version is unused + 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); @@ -312,7 +318,7 @@ class ImageOverlay Error ImageOverlay::parse(size_t num_images, const std::vector& data) { Error eofError(heif_error_Invalid_input, - heif_suberror_Invalid_grid_data, + heif_suberror_Invalid_overlay_data, "Overlay image data incomplete"); if (data.size() < 2 + 4 * 2) { @@ -320,17 +326,17 @@ Error ImageOverlay::parse(size_t num_images, const std::vector& data) } 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()); + 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; @@ -346,6 +352,12 @@ Error ImageOverlay::parse(size_t num_images, const std::vector& data) 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++) { @@ -399,8 +411,7 @@ void ImageOverlay::get_offset(size_t image_index, int32_t* x, int32_t* y) const HeifContext::HeifContext() { - m_maximum_image_width_limit = MAX_IMAGE_WIDTH; - m_maximum_image_height_limit = MAX_IMAGE_HEIGHT; + m_maximum_image_size_limit = MAX_IMAGE_SIZE; reset_to_empty_heif(); } @@ -457,6 +468,29 @@ void HeifContext::reset_to_empty_heif() m_primary_image.reset(); } +Error HeifContext::check_resolution(uint32_t width, uint32_t height) const { + // --- check whether the image size is "too large" + uint32_t max_width_height = static_cast(std::numeric_limits::max()); + if ((width > max_width_height || height > max_width_height) || + (height != 0 && width > m_maximum_image_size_limit / height)) { + std::stringstream sstr; + sstr << "Image size " << width << "x" << height << " exceeds the maximum image size " + << m_maximum_image_size_limit << "\n"; + + return Error(heif_error_Memory_allocation_error, + heif_suberror_Security_limit_exceeded, + sstr.str()); + } + + if (width==0 || height==0) { + return Error(heif_error_Memory_allocation_error, + heif_suberror_Invalid_image_size, + "zero width or height"); + } + + return Error::Ok; +} + 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"); @@ -593,18 +627,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"; + << m_maximum_image_size_limit << "\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); @@ -750,9 +781,14 @@ Error HeifContext::interpret_heif_file() for (heif_item_id ref: refs) { auto master_iter = m_all_images.find(ref); if (master_iter == m_all_images.end()) { - return Error(heif_error_Invalid_input, - heif_suberror_Nonexisting_item_referenced, - "Non-existing alpha image referenced"); + + if (!m_heif_file->has_item_with_id(ref)) { + return Error(heif_error_Invalid_input, + heif_suberror_Nonexisting_item_referenced, + "Non-existing alpha image referenced"); + } + + continue; } auto master_img = master_iter->second; @@ -778,9 +814,14 @@ Error HeifContext::interpret_heif_file() for (heif_item_id ref: refs) { auto master_iter = m_all_images.find(ref); if (master_iter == m_all_images.end()) { - return Error(heif_error_Invalid_input, - heif_suberror_Nonexisting_item_referenced, - "Non-existing depth image referenced"); + + if (!m_heif_file->has_item_with_id(ref)) { + return Error(heif_error_Invalid_input, + heif_suberror_Nonexisting_item_referenced, + "Non-existing depth image referenced"); + } + + continue; } if (image.get() == master_iter->second.get()) { return Error(heif_error_Invalid_input, @@ -1343,6 +1384,16 @@ Error HeifContext::decode_image_planar(heif_item_id ID, Error error; + // --- check whether image size exceeds maximum (according to 'ispe') + + auto ispe = m_heif_file->get_property(ID); + if (ispe) { + error = check_resolution(ispe->get_width(), ispe->get_height()); + if (error) { + return error; + } + } + // --- decode image, depending on its type if (image_type == "hvc1" || @@ -1352,6 +1403,12 @@ Error HeifContext::decode_image_planar(heif_item_id ID, image_type == "jpeg" || (image_type == "mime" && m_heif_file->get_content_type(ID) == "image/jpeg")) { + std::vector data; + error = m_heif_file->get_compressed_image_data(ID, &data); + if (error) { + return error; + } + heif_compression_format compression = heif_compression_undefined; if (image_type == "hvc1") { compression = heif_compression_HEVC; @@ -1367,7 +1424,14 @@ Error HeifContext::decode_image_planar(heif_item_id ID, compression = heif_compression_JPEG; } else if (image_type == "j2k1") { - compression = heif_compression_JPEG2000; + JPEG2000MainHeader j2k_header; + j2k_header.setHeaderData(data); + j2k_header.doParse(); + if (j2k_header.hasHighThroughputExtension()) { + compression = heif_compression_HTJ2K; + } else { + compression = heif_compression_JPEG2000; + } } const struct heif_decoder_plugin* decoder_plugin = get_decoder(compression, options.decoder_id); @@ -1375,11 +1439,7 @@ Error HeifContext::decode_image_planar(heif_item_id ID, 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); @@ -1500,11 +1560,9 @@ Error HeifContext::decode_image_planar(heif_item_id ID, if (error) { return error; } - error = UncompressedImageCodec::decode_uncompressed_image(m_heif_file, + error = UncompressedImageCodec::decode_uncompressed_image(this, ID, img, - m_maximum_image_width_limit, - m_maximum_image_height_limit, data); if (error) { return error; @@ -1518,11 +1576,9 @@ Error HeifContext::decode_image_planar(heif_item_id ID, std::cout << "mski error 1" << std::endl; return error; } - error = MaskImageCodec::decode_mask_image(m_heif_file, + error = MaskImageCodec::decode_mask_image(this, ID, img, - m_maximum_image_width_limit, - m_maximum_image_height_limit, data); if (error) { return error; @@ -1774,17 +1830,11 @@ Error HeifContext::decode_full_grid_image(heif_item_id ID, // --- 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()); + err = check_resolution(w, h); + if (err) { + return err; } - img = std::make_shared(); img->create(w, h, heif_colorspace_RGB, @@ -1849,7 +1899,7 @@ Error HeifContext::decode_full_grid_image(heif_item_id ID, img->add_plane(heif_channel_B, w, h, bpp); } - int y0 = 0; + uint32_t y0 = 0; int reference_idx = 0; #if ENABLE_PARALLEL_TILE_DECODING @@ -1857,7 +1907,7 @@ Error HeifContext::decode_full_grid_image(heif_item_id ID, struct tile_data { heif_item_id tileID; - int x_origin, y_origin; + uint32_t x_origin, y_origin; }; std::deque tiles; @@ -1867,24 +1917,48 @@ Error HeifContext::decode_full_grid_image(heif_item_id ID, std::deque > errs; #endif - for (int y = 0; y < grid.get_rows(); y++) { - int x0 = 0; - int tile_height = 0; + uint32_t tile_width=0; + uint32_t tile_height=0; - for (int x = 0; x < grid.get_columns(); x++) { + 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]; 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"); + return {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(); + uint32_t src_width = tileImg->get_width(); + uint32_t src_height = tileImg->get_height(); + err = check_resolution(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 {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 {heif_error_Invalid_input, + heif_suberror_Invalid_grid_data, + "Grid tiles have different sizes"}; + } #if ENABLE_PARALLEL_TILE_DECODING if (m_max_decoding_threads > 0) @@ -1894,14 +1968,13 @@ Error HeifContext::decode_full_grid_image(heif_item_id ID, if (1) #endif { - Error err = decode_and_paste_tile_image(tileID, img, x0, y0, options); + 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++; } @@ -1957,7 +2030,7 @@ Error HeifContext::decode_full_grid_image(heif_item_id ID, Error HeifContext::decode_and_paste_tile_image(heif_item_id tileID, const std::shared_ptr& img, - int x0, int y0, + uint32_t x0, uint32_t y0, const heif_decoding_options& options) const { std::shared_ptr tile_img; @@ -1967,16 +2040,14 @@ Error HeifContext::decode_and_paste_tile_image(heif_item_id tileID, return err; } - const int w = img->get_width(); - const int h = img->get_height(); + const uint32_t w = img->get_width(); + const uint32_t 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); + uint32_t src_width = tile_img->get_width(); + uint32_t src_height = tile_img->get_height(); heif_chroma chroma = img->get_chroma_format(); @@ -2124,14 +2195,9 @@ Error HeifContext::decode_overlay_image(heif_item_id ID, 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()); + err = check_resolution(w, h); + if (err) { + return err; } // TODO: seems we always have to compose this in RGB since the background color is an RGB value @@ -2390,6 +2456,59 @@ Error HeifContext::encode_image(const std::shared_ptr& pixel_ima 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) { @@ -3700,9 +3819,18 @@ Error HeifContext::add_generic_metadata(const std::shared_ptr& master_ima 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, diff --git a/libheif/context.h b/libheif/context.h index d914ab133a..af1d7d23dd 100644 --- a/libheif/context.h +++ b/libheif/context.h @@ -30,8 +30,8 @@ #include "error.h" -#include "heif.h" -#include "heif_plugin.h" +#include "libheif/heif.h" +#include "libheif/heif_plugin.h" #include "bitstream.h" #include "box.h" // only for color_profile, TODO: maybe move the color_profiles to its own header @@ -70,10 +70,11 @@ class HeifContext : public ErrorBuffer void set_max_decoding_threads(int max_threads) { m_max_decoding_threads = max_threads; } + // Sets the maximum size of both width and height of an image. The total limit + // of the image size (width * height) will be "maximum_size * maximum_size". void set_maximum_image_size_limit(int maximum_size) { - m_maximum_image_width_limit = maximum_size; - m_maximum_image_height_limit = maximum_size; + m_maximum_image_size_limit = int64_t(maximum_size) * maximum_size; } Error read(const std::shared_ptr& reader); @@ -97,6 +98,10 @@ class HeifContext : public ErrorBuffer m_aux_images.clear(); } + Error check_resolution(uint32_t w, uint32_t h) const { + return m_heif_context->check_resolution(w, h); + } + void set_resolution(int w, int h) { m_width = w; @@ -341,7 +346,9 @@ class HeifContext : public ErrorBuffer Box_cmex::ExtrinsicMatrix m_extrinsic_matrix{}; }; - std::shared_ptr get_heif_file() { return m_heif_file; } + Error check_resolution(uint32_t width, uint32_t height) const; + + std::shared_ptr get_heif_file() const { return m_heif_file; } std::vector> get_top_level_images() { return m_top_level_images; } @@ -409,6 +416,13 @@ class HeifContext : public ErrorBuffer 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, @@ -515,8 +529,8 @@ class HeifContext : public ErrorBuffer int m_max_decoding_threads = 4; - uint32_t m_maximum_image_width_limit; - uint32_t m_maximum_image_height_limit; + // Maximum image size in pixels (width * height). + uint64_t m_maximum_image_size_limit; std::vector> m_region_items; @@ -531,7 +545,7 @@ class HeifContext : public ErrorBuffer Error decode_and_paste_tile_image(heif_item_id tileID, const std::shared_ptr& out_image, - int x0, int y0, + uint32_t x0, uint32_t y0, const heif_decoding_options& options) const; Error decode_derived_image(heif_item_id ID, diff --git a/libheif/error.cc b/libheif/error.cc index 2735c12981..1caa5348fc 100644 --- a/libheif/error.cc +++ b/libheif/error.cc @@ -168,12 +168,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 +216,8 @@ 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"; // --- Encoder_plugin_error -- diff --git a/libheif/error.h b/libheif/error.h index af2af88f07..9a68dd4f23 100644 --- a/libheif/error.h +++ b/libheif/error.h @@ -32,8 +32,7 @@ #include #include - -#include "heif.h" +#include "libheif/heif.h" static constexpr char kSuccess[] = "Success"; diff --git a/libheif/file.cc b/libheif/file.cc index 9ae765ce30..92bdf615bf 100644 --- a/libheif/file.cc +++ b/libheif/file.cc @@ -19,12 +19,14 @@ */ #include "file.h" -#include "libheif/box.h" +#include "box.h" #include "libheif/heif.h" #include "libheif/heif_properties.h" -#include "libheif/jpeg2000.h" -#include "libheif/jpeg.h" -#include "libheif/vvc.h" +#include "compression.h" +#include "codecs/jpeg2000.h" +#include "codecs/jpeg.h" +#include "codecs/vvc.h" +#include "codecs/uncompressed_box.h" #include #include @@ -44,11 +46,9 @@ #include #endif -#include "metadata_compression.h" -#include "jpeg2000.h" #if WITH_UNCOMPRESSED_CODEC -#include "uncompressed_image.h" +#include "codecs/uncompressed_image.h" #endif // TODO: make this a decoder option @@ -781,211 +781,408 @@ Error HeifFile::get_compressed_image_data(heif_item_id ID, std::vector* 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; + return get_compressed_image_data_hvc1(ID, data, item); + } + else if (item_type == "vvc1") { + // --- --- --- VVC + return get_compressed_image_data_vvc(ID, data, item); + } + else if (item_type == "av01") { + return get_compressed_image_data_av1(ID, data, item); + } + else if (item_type == "jpeg" || + (item_type == "mime" && get_content_type(ID) == "image/jpeg")) { + return get_compressed_image_data_jpeg(ID, data, item); + } + else if (item_type == "j2k1") { + return get_compressed_image_data_jpeg2000(ID, item, data); + } +#if WITH_UNCOMPRESSED_CODEC + else if (item_type == "unci") { + return get_compressed_image_data_uncompressed(ID, data, item); + } +#endif + 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")) { + Error error; + bool read_uncompressed = true; + if (item_type == "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(*item, m_input_stream, m_idat_box, &compressed_data); + if (error) { + return error; } + error = decompress_zlib(compressed_data, data); + if (error) { + return error; + } +#else + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_header_compression_method, + encoding); +#endif + } + else if (encoding == "deflate") { +#if HAVE_ZLIB + read_uncompressed = false; + std::vector compressed_data; + error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, &compressed_data); + if (error) { + return error; + } + 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 + } + else if (encoding == "br") { +#if HAVE_BROTLI + read_uncompressed = false; + std::vector compressed_data; + error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, &compressed_data); + if (error) { + return error; + } + 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 } } - 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); + if (read_uncompressed) { + return m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); } + } + return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_codec); +} - error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); +#if WITH_UNCOMPRESSED_CODEC +// generic compression and uncompressed, per 23001-17 +const Error HeifFile::get_compressed_image_data_uncompressed(heif_item_id ID, std::vector *data, const Box_iloc::Item *item) const +{ + std::vector> properties; + Error err = m_ipco_box->get_properties_for_item_ID(ID, m_ipma_box, properties); + if (err) { + return err; } - else if (item_type == "vvc1") { - // --- --- --- VVC - // --- get properties for this image + // --- get codec configuration - std::vector> properties; - Error err = m_ipco_box->get_properties_for_item_ID(ID, m_ipma_box, properties); - if (err) { - return err; + std::shared_ptr cmpC_box; + std::shared_ptr icbr_box; + for (auto& prop : properties) { + if (prop->get_short_type() == fourcc("cmpC")) { + cmpC_box = std::dynamic_pointer_cast(prop); } - - // --- 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 (prop->get_short_type() == fourcc("icbr")) { + icbr_box = std::dynamic_pointer_cast(prop); } - - 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); + if (cmpC_box && icbr_box) { + break; } - else if (!vvcC_box->get_headers(data)) { + } + if (!cmpC_box) { + // assume no generic compression + return m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); + } + if (!cmpC_box->get_must_decompress_individual_entities()) { + std::vector compressed_data; + m_iloc_box->read_data(*item, m_input_stream, m_idat_box, &compressed_data); + return do_decompress_data(cmpC_box, compressed_data, data); + } else { + if (!icbr_box) { + std::stringstream sstr; + sstr << "cannot decode unci item requiring entity decompression without icbr box" << std::endl; return Error(heif_error_Invalid_input, - heif_suberror_No_item_data); + heif_suberror_No_icbr_box, + sstr.str()); + } + if (item->construction_method == 0) { + for (Box_icbr::ByteRange range: icbr_box->get_ranges()) { + // TODO: check errors + bool success = m_input_stream->seek(range.range_offset); + if (!success) { + return Error{heif_error_Invalid_input, heif_suberror_End_of_data, "error while seeking to generically compressed data"}; + } + std::vector compressed_range_bytes(range.range_size); + success = m_input_stream->read((char*) compressed_range_bytes.data(), static_cast(compressed_range_bytes.size())); + if (!success) { + return Error{heif_error_Invalid_input, heif_suberror_End_of_data, "error while reading generically compressed data"}; + } + std::vector uncompressed_range_data; + Error err = do_decompress_data(cmpC_box, compressed_range_bytes, &uncompressed_range_data); + if (err) { + return err; + } + data->insert(data->end(), uncompressed_range_data.data(), uncompressed_range_data.data() + uncompressed_range_data.size()); + } + return Error::Ok; + } else { + // TODO: implement... + std::stringstream sstr; + sstr << "cannot decode unci item from idat yet" << std::endl; + return Error(heif_error_Unsupported_feature, + heif_suberror_Unsupported_data_version, + sstr.str()); } - - error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); } - else if (item_type == "av01") { - // --- --- --- AV1 + return Error(heif_error_Unsupported_feature, heif_suberror_Unsupported_codec); +} - // --- get properties for this image +const Error HeifFile::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()); + } +} +#endif - std::vector> properties; - Error err = m_ipco_box->get_properties_for_item_ID(ID, m_ipma_box, properties); - if (err) { - return err; - } +const Error HeifFile::get_compressed_image_data_hvc1(heif_item_id ID, std::vector *data, const Box_iloc::Item *item) const +{ + // --- 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 + // --- 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; - } + 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 (!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); + 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 (item_type == "jpeg" || - (item_type == "mime" && get_content_type(ID) == "image/jpeg")) { + else if (!hvcC_box->get_headers(data)) + { + return Error(heif_error_Invalid_input, + heif_suberror_No_item_data); + } + + return m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); +} - // --- 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; - } +const Error HeifFile::get_compressed_image_data_vvc(heif_item_id ID, std::vector *data, const Box_iloc::Item *item) const +{ - // --- get codec configuration + // --- get properties for this image - 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; - } + 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; } } + } - error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); + if (!vvcC_box) + { + 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); } - 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 + return m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); +} - 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; - } +const Error HeifFile::get_compressed_image_data_av1(heif_item_id ID, std::vector *data, const Box_iloc::Item *item) const +{ + // --- --- --- 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 (!j2kH_box) { - // Should always have an j2kH box, because we are checking this in - // heif_context::interpret_heif_file() + if (!av1C_box) + { + 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); + } - //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 m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); +} - error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); +const Error HeifFile::get_compressed_image_data_jpeg2000(heif_item_id ID, const Box_iloc::Item *item, std::vector *data) const +{ + std::vector> properties; + Error err = m_ipco_box->get_properties_for_item_ID(ID, m_ipma_box, properties); + if (err) + { + return err; } - 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 + // --- 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 (read_uncompressed) { - error = m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); - } + 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); + // } - if (error != Error::Ok) { - return error; + return m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); +} + +const Error HeifFile::get_compressed_image_data_jpeg(heif_item_id ID, std::vector * data, const Box_iloc::Item *item) const +{ + // --- 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; } - return Error::Ok; + // --- 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; + } + } + } + + return m_iloc_box->read_data(*item, m_input_stream, m_idat_box, data); } @@ -1047,9 +1244,15 @@ Error HeifFile::get_item_data(heif_item_id ID, std::vector* out_data, h return m_iloc_box->read_data(*item, m_input_stream, m_idat_box, out_data); } + 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; } @@ -1074,10 +1277,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}; @@ -1490,15 +1698,25 @@ Error HeifFile::set_item_data(const std::shared_ptr& item, const uint8 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 diff --git a/libheif/file.h b/libheif/file.h index 2f85a5a8e9..e6c8224446 100644 --- a/libheif/file.h +++ b/libheif/file.h @@ -21,11 +21,12 @@ #ifndef LIBHEIF_FILE_H #define LIBHEIF_FILE_H -#include "avif.h" #include "box.h" -#include "hevc.h" -#include "vvc.h" #include "nclx.h" +#include "codecs/avif.h" +#include "codecs/hevc.h" +#include "codecs/vvc.h" +#include "codecs/uncompressed_box.h" #include #include @@ -86,7 +87,7 @@ class HeifFile Error get_compressed_image_data(heif_item_id ID, std::vector* out_data) const; - Error get_item_data(heif_item_id ID, std::vector* out_data, heif_metadata_compression* out_compression) const; + 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; } @@ -251,6 +252,22 @@ class HeifFile std::unordered_set& parent_items) const; int jpeg_get_bits_per_pixel(heif_item_id imageID) const; + + const Error get_compressed_image_data_hvc1(heif_item_id ID, std::vector *data, const Box_iloc::Item *item) const; + + const Error get_compressed_image_data_vvc(heif_item_id ID, std::vector *data, const Box_iloc::Item *item) const; + +#if WITH_UNCOMPRESSED_CODEC + const Error get_compressed_image_data_uncompressed(heif_item_id ID, std::vector *data, const Box_iloc::Item *item) const; + + const Error do_decompress_data(std::shared_ptr &cmpC_box, std::vector compressed_data, std::vector *data) const; +#endif + + const Error get_compressed_image_data_av1(heif_item_id ID, std::vector *data, const Box_iloc::Item *item) const; + + const Error get_compressed_image_data_jpeg2000(heif_item_id ID, const Box_iloc::Item *item, std::vector *data) const; + + const Error get_compressed_image_data_jpeg(heif_item_id ID, std::vector *data, const Box_iloc::Item *item) const; }; #endif diff --git a/libheif/init.cc b/libheif/init.cc index 3ff4f5bccf..bbd5f6c4af 100644 --- a/libheif/init.cc +++ b/libheif/init.cc @@ -19,11 +19,11 @@ */ #include "init.h" -#include "heif.h" +#include "libheif/heif.h" #include "error.h" #include "plugin_registry.h" #include "common_utils.h" -#include "libheif/color-conversion/colorconversion.h" +#include "color-conversion/colorconversion.h" #if ENABLE_MULTITHREADING_SUPPORT diff --git a/libheif/pixelimage.cc b/libheif/pixelimage.cc index 9fd69366d7..257f54025e 100644 --- a/libheif/pixelimage.cc +++ b/libheif/pixelimage.cc @@ -25,6 +25,7 @@ #include #include #include +#include heif_chroma chroma_from_subsampling(int h, int v) @@ -713,9 +714,9 @@ Error HeifPixelImage::fill_RGB_16bit(uint16_t r, uint16_t g, uint16_t b, uint16_ "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; + size_t stride = plane.stride; uint8_t* data = plane.mem; uint16_t val16; @@ -741,14 +742,42 @@ 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(); @@ -773,70 +802,88 @@ Error HeifPixelImage::overlay(std::shared_ptr& overlay, int dx, 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; + } + + if (dx < 0) { + // overlay image started partially outside of left border - // 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"); + 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]; diff --git a/libheif/pixelimage.h b/libheif/pixelimage.h index 429e50e977..096cb01dad 100644 --- a/libheif/pixelimage.h +++ b/libheif/pixelimage.h @@ -22,7 +22,7 @@ #ifndef LIBHEIF_IMAGE_H #define LIBHEIF_IMAGE_H -#include "heif.h" +//#include "heif.h" #include "error.h" #include "nclx.h" @@ -109,7 +109,7 @@ class HeifPixelImage : public std::enable_shared_from_this, 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; diff --git a/libheif/plugin_registry.cc b/libheif/plugin_registry.cc index 897fa4780b..7ffae06438 100644 --- a/libheif/plugin_registry.cc +++ b/libheif/plugin_registry.cc @@ -27,69 +27,81 @@ #if HAVE_LIBDE265 -#include "libheif/plugins/decoder_libde265.h" +#include "plugins/decoder_libde265.h" #endif #if HAVE_X265 -#include "libheif/plugins/encoder_x265.h" +#include "plugins/encoder_x265.h" #endif #if HAVE_KVAZAAR -#include "libheif/plugins/encoder_kvazaar.h" +#include "plugins/encoder_kvazaar.h" #endif #if HAVE_UVG266 -#include "libheif/plugins/encoder_uvg266.h" +#include "plugins/encoder_uvg266.h" +#endif + +#if HAVE_VVDEC +#include "plugins/decoder_vvdec.h" +#endif + +#if HAVE_VVENC +#include "plugins/encoder_vvenc.h" #endif #if HAVE_AOM_ENCODER -#include "libheif/plugins/encoder_aom.h" +#include "plugins/encoder_aom.h" #endif #if HAVE_AOM_DECODER -#include "libheif/plugins/decoder_aom.h" +#include "plugins/decoder_aom.h" #endif #if HAVE_RAV1E -#include "libheif/plugins/encoder_rav1e.h" +#include "plugins/encoder_rav1e.h" #endif #if HAVE_DAV1D -#include "libheif/plugins/decoder_dav1d.h" +#include "plugins/decoder_dav1d.h" #endif #if HAVE_SvtEnc -#include "libheif/plugins/encoder_svt.h" +#include "plugins/encoder_svt.h" #endif #if HAVE_FFMPEG_DECODER -#include "libheif/plugins/decoder_ffmpeg.h" +#include "plugins/decoder_ffmpeg.h" #endif #if WITH_UNCOMPRESSED_CODEC -#include "libheif/plugins/encoder_uncompressed.h" +#include "plugins/encoder_uncompressed.h" #endif #if HAVE_JPEG_DECODER -#include "libheif/plugins/decoder_jpeg.h" +#include "plugins/decoder_jpeg.h" #endif #if HAVE_JPEG_ENCODER -#include "libheif/plugins/encoder_jpeg.h" +#include "plugins/encoder_jpeg.h" #endif #if HAVE_OPENJPEG_ENCODER -#include "libheif/plugins/encoder_openjpeg.h" +#include "plugins/encoder_openjpeg.h" #endif #if HAVE_OPENJPEG_DECODER -#include "libheif/plugins/decoder_openjpeg.h" +#include "plugins/decoder_openjpeg.h" #endif -#include "libheif/plugins/encoder_mask.h" +#include "plugins/encoder_mask.h" #if HAVE_OPENJPH_ENCODER -#include "libheif/plugins/encoder_openjph.h" +#include "plugins/encoder_openjph.h" +#endif + +#if HAVE_OPENJPH_DECODER +#include "plugins/decoder_openjph.h" #endif std::set s_decoder_plugins; @@ -143,6 +155,14 @@ 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 + #if HAVE_AOM_ENCODER register_encoder(get_encoder_plugin_aom()); #endif @@ -187,6 +207,10 @@ void register_default_plugins() register_encoder(get_encoder_plugin_openjph()); #endif +#if HAVE_OPENJPH_DECODER + register_decoder(get_decoder_plugin_openjph()); +#endif + #if WITH_UNCOMPRESSED_CODEC register_encoder(get_encoder_plugin_uncompressed()); #endif diff --git a/libheif/plugin_registry.h b/libheif/plugin_registry.h index deb92c7fc4..962a47cff5 100644 --- a/libheif/plugin_registry.h +++ b/libheif/plugin_registry.h @@ -29,8 +29,8 @@ #include "error.h" -#include "heif.h" -#include "heif_plugin.h" +#include "libheif/heif.h" +#include "libheif/heif_plugin.h" struct heif_encoder_descriptor diff --git a/libheif/plugins/CMakeLists.txt b/libheif/plugins/CMakeLists.txt index 6b9cd700ba..b856202a7e 100644 --- a/libheif/plugins/CMakeLists.txt +++ b/libheif/plugins/CMakeLists.txt @@ -5,7 +5,7 @@ macro(plugin_compilation name varName foundName optionName defineName) message("Compiling '" ${name} "' as dynamic plugin") add_library(heif-${name} MODULE ${${optionName}_sources} ${${optionName}_extra_plugin_sources} - ../heif_plugin.cc + ../api/libheif/heif_plugin.cc ) if (NOT APPLE) set_target_properties(heif-${name} @@ -18,7 +18,7 @@ macro(plugin_compilation name varName foundName optionName defineName) LIBHEIF_EXPORTS HAVE_VISIBILITY) target_compile_definitions(heif-${name} PRIVATE PLUGIN_${defineName}=1) - target_include_directories(heif-${name} PRIVATE ${PROJECT_SOURCE_DIR} ${libheif_BINARY_DIR} ${libheif_SOURCE_DIR} ${${varName}_INCLUDE_DIRS}) + target_include_directories(heif-${name} PRIVATE ${PROJECT_SOURCE_DIR} ${libheif_BINARY_DIR} ${libheif_SOURCE_DIR}/libheif ${libheif_SOURCE_DIR}/libheif/api ${${varName}_INCLUDE_DIRS}) target_link_libraries(heif-${name} PRIVATE ${${varName}_LIBRARIES} heif) install(TARGETS heif-${name} @@ -96,10 +96,22 @@ set(OPENJPH_ENCODER_sources encoder_openjph.cc encoder_openjph.h) set(OPENJPH_ENCODER_extra_plugin_sources) plugin_compilation(jphenc OPENJPH OPENJPH_FOUND OPENJPH_ENCODER OPENJPH_ENCODER) +set(OPENJPH_DECODER_sources decoder_openjph.cc decoder_openjph.h) +set(OPENJPH_DECODER_extra_plugin_sources) +plugin_compilation(jphdec OPENJPH OPENJPH_FOUND OPENJPH_DECODER OPENJPH_DECODER) + set(UVG266_sources encoder_uvg266.cc encoder_uvg266.h) set(UVG266_extra_plugin_sources) plugin_compilation(uvg266 UVG266 UVG266_FOUND UVG266 UVG266) +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) + target_sources(heif PRIVATE encoder_mask.h encoder_mask.cc) diff --git a/libheif/plugins/decoder_aom.h b/libheif/plugins/decoder_aom.h index a5f70b8de0..23ed80b9eb 100644 --- a/libheif/plugins/decoder_aom.h +++ b/libheif/plugins/decoder_aom.h @@ -21,7 +21,7 @@ #ifndef LIBHEIF_DECODER_AOM_H #define LIBHEIF_DECODER_AOM_H -#include "libheif/common_utils.h" +#include "common_utils.h" const struct heif_decoder_plugin* get_decoder_plugin_aom(); diff --git a/libheif/plugins/decoder_dav1d.cc b/libheif/plugins/decoder_dav1d.cc index 9e46f1fe53..5a0afcd8f6 100644 --- a/libheif/plugins/decoder_dav1d.cc +++ b/libheif/plugins/decoder_dav1d.cc @@ -20,8 +20,8 @@ #include "libheif/heif.h" #include "libheif/heif_plugin.h" -#include "libheif/security_limits.h" -#include "libheif/common_utils.h" +#include "security_limits.h" +#include "common_utils.h" #include "decoder_dav1d.h" #include #include @@ -90,7 +90,7 @@ struct heif_error dav1d_new_decoder(void** dec) dav1d_default_settings(&decoder->settings); - decoder->settings.frame_size_limit = MAX_IMAGE_WIDTH * MAX_IMAGE_HEIGHT; + decoder->settings.frame_size_limit = MAX_IMAGE_SIZE; decoder->settings.all_layers = 0; if (dav1d_open(&decoder->context, &decoder->settings) != 0) { diff --git a/libheif/plugins/decoder_dav1d.h b/libheif/plugins/decoder_dav1d.h index f18c0ccae9..e32d8b4ff2 100644 --- a/libheif/plugins/decoder_dav1d.h +++ b/libheif/plugins/decoder_dav1d.h @@ -21,7 +21,7 @@ #ifndef LIBHEIF_DECODER_DAV1D_H #define LIBHEIF_DECODER_DAV1D_H -#include "libheif/common_utils.h" +#include "common_utils.h" const struct heif_decoder_plugin* get_decoder_plugin_dav1d(); diff --git a/libheif/plugins/decoder_ffmpeg.cc b/libheif/plugins/decoder_ffmpeg.cc index 04fa255d80..abf27aeecf 100644 --- a/libheif/plugins/decoder_ffmpeg.cc +++ b/libheif/plugins/decoder_ffmpeg.cc @@ -26,8 +26,6 @@ #include "config.h" #endif -#include -#include #include extern "C" @@ -187,6 +185,60 @@ static struct heif_error ffmpeg_v1_push_data(void* decoder_raw, const void* data return heif_error_success; } +static heif_chroma ffmpeg_get_chroma_format(enum AVPixelFormat pix_fmt) { + if (pix_fmt == AV_PIX_FMT_GRAY8) + { + return heif_chroma_monochrome; + } + else if ((pix_fmt == AV_PIX_FMT_YUV420P) || (pix_fmt == AV_PIX_FMT_YUVJ420P) || + (pix_fmt == AV_PIX_FMT_YUV420P10LE)) + { + return heif_chroma_420; + } + else if (pix_fmt == AV_PIX_FMT_YUV422P) + { + return heif_chroma_422; + } + else if (pix_fmt == AV_PIX_FMT_YUV444P) + { + return heif_chroma_444; + } + // Unsupported pix_fmt + return heif_chroma_undefined; +} + +static int ffmpeg_get_chroma_width(const AVFrame* frame, heif_channel channel, heif_chroma chroma) +{ + if (channel == heif_channel_Y) + { + return frame->width; + } + else if (chroma == heif_chroma_420 || chroma == heif_chroma_422) + { + return (frame->width + 1) / 2; + } + else + { + return frame->width; + } +} + +static int ffmpeg_get_chroma_height(const AVFrame* frame, heif_channel channel, heif_chroma chroma) +{ + if (channel == heif_channel_Y) + { + return frame->height; + } + else if (chroma == heif_chroma_420) + { + return (frame->height + 1) / 2; + } + else + { + return frame->height; + } +} + static struct heif_error hevc_decode(AVCodecContext* hevc_dec_ctx, AVFrame* hevc_frame, AVPacket* hevc_pkt, struct heif_image** image) { int ret; @@ -209,14 +261,16 @@ static struct heif_error hevc_decode(AVCodecContext* hevc_dec_ctx, AVFrame* hevc } - if ((hevc_dec_ctx->pix_fmt == AV_PIX_FMT_YUV420P) || (hevc_dec_ctx->pix_fmt == AV_PIX_FMT_YUVJ420P) || //planar YUV 4:2:0, 12bpp, (1 Cr & Cb sample per 2x2 Y samples) - (hevc_dec_ctx->pix_fmt == AV_PIX_FMT_YUV420P10LE)) + heif_chroma chroma = ffmpeg_get_chroma_format(hevc_dec_ctx->pix_fmt); + if (chroma != heif_chroma_undefined) { + bool is_mono = (chroma == heif_chroma_monochrome); + heif_error err; err = heif_image_create(hevc_frame->width, hevc_frame->height, - heif_colorspace_YCbCr, - heif_chroma_420, + is_mono ? heif_colorspace_monochrome : heif_colorspace_YCbCr, + chroma, image); if (err.code) { return err; @@ -228,7 +282,7 @@ static struct heif_error hevc_decode(AVCodecContext* hevc_dec_ctx, AVFrame* hevc heif_channel_Cr }; - int nPlanes = 3; + int nPlanes = is_mono ? 1 : 3; for (int channel = 0; channel < nPlanes; channel++) { @@ -236,8 +290,8 @@ static struct heif_error hevc_decode(AVCodecContext* hevc_dec_ctx, AVFrame* hevc int stride = hevc_frame->linesize[channel]; const uint8_t* data = hevc_frame->data[channel]; - int w = (channel == 0) ? hevc_frame->width : hevc_frame->width >> 1; - int h = (channel == 0) ? hevc_frame->height : hevc_frame->height >> 1; + int w = ffmpeg_get_chroma_width(hevc_frame, channel2plane[channel], chroma); + int h = ffmpeg_get_chroma_height(hevc_frame, channel2plane[channel], chroma); if (w <= 0 || h <= 0) { heif_image_release(*image); err = { heif_error_Decoder_plugin_error, diff --git a/libheif/plugins/decoder_ffmpeg.h b/libheif/plugins/decoder_ffmpeg.h index 4b0161a6cc..d4ee87bf79 100644 --- a/libheif/plugins/decoder_ffmpeg.h +++ b/libheif/plugins/decoder_ffmpeg.h @@ -21,7 +21,7 @@ #ifndef LIBHEIF_HEIF_DECODER_FFMPEG_H #define LIBHEIF_HEIF_DECODER_FFMPEG_H -#include "libheif/common_utils.h" +#include "common_utils.h" const struct heif_decoder_plugin* get_decoder_plugin_ffmpeg(); diff --git a/libheif/plugins/decoder_jpeg.h b/libheif/plugins/decoder_jpeg.h index 9d63b74cfa..d40e441b15 100644 --- a/libheif/plugins/decoder_jpeg.h +++ b/libheif/plugins/decoder_jpeg.h @@ -21,7 +21,7 @@ #ifndef LIBHEIF_DECODER_JPEG_H #define LIBHEIF_DECODER_JPEG_H -#include "libheif/common_utils.h" +#include "common_utils.h" const struct heif_decoder_plugin* get_decoder_plugin_jpeg(); diff --git a/libheif/plugins/decoder_libde265.cc b/libheif/plugins/decoder_libde265.cc index 1178b2ba9f..6bf5e3f7b9 100644 --- a/libheif/plugins/decoder_libde265.cc +++ b/libheif/plugins/decoder_libde265.cc @@ -281,7 +281,7 @@ static struct heif_error libde265_v1_push_data(void* decoder_raw, const void* da return err; } - uint32_t nal_size = (cdata[ptr] << 24) | (cdata[ptr + 1] << 16) | (cdata[ptr + 2] << 8) | (cdata[ptr + 3]); + uint32_t nal_size = static_cast((cdata[ptr] << 24) | (cdata[ptr + 1] << 16) | (cdata[ptr + 2] << 8) | (cdata[ptr + 3])); ptr += 4; if (nal_size > size - ptr) { diff --git a/libheif/plugins/decoder_libde265.h b/libheif/plugins/decoder_libde265.h index 0b2e8c3e69..ec93a45cc8 100644 --- a/libheif/plugins/decoder_libde265.h +++ b/libheif/plugins/decoder_libde265.h @@ -21,7 +21,7 @@ #ifndef LIBHEIF_HEIF_DECODER_DE265_H #define LIBHEIF_HEIF_DECODER_DE265_H -#include "libheif/common_utils.h" +#include "common_utils.h" const struct heif_decoder_plugin* get_decoder_plugin_libde265(); diff --git a/libheif/plugins/decoder_openjpeg.cc b/libheif/plugins/decoder_openjpeg.cc index 7e801bff00..255c9601e2 100644 --- a/libheif/plugins/decoder_openjpeg.cc +++ b/libheif/plugins/decoder_openjpeg.cc @@ -62,7 +62,7 @@ static void openjpeg_deinit_plugin() static int openjpeg_does_support_format(enum heif_compression_format format) { - if (format == heif_compression_JPEG2000) { + if ((format == heif_compression_JPEG2000) || (format == heif_compression_HTJ2K)) { return OPENJPEG_PLUGIN_PRIORITY; } else { diff --git a/libheif/plugins/decoder_openjpeg.h b/libheif/plugins/decoder_openjpeg.h index a93cfb0492..8380acb9e5 100644 --- a/libheif/plugins/decoder_openjpeg.h +++ b/libheif/plugins/decoder_openjpeg.h @@ -22,7 +22,7 @@ #ifndef LIBHEIF_DECODER_OPENJPEG_H #define LIBHEIF_DECODER_OPENJPEG_H -#include "libheif/common_utils.h" +#include "common_utils.h" const struct heif_decoder_plugin* get_decoder_plugin_openjpeg(); diff --git a/libheif/plugins/decoder_openjph.cc b/libheif/plugins/decoder_openjph.cc new file mode 100644 index 0000000000..342acf2738 --- /dev/null +++ b/libheif/plugins/decoder_openjph.cc @@ -0,0 +1,238 @@ +/* + * OpenJPH High Throughput JPEG 2000 decoder. + * + * 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. + * + */ + +#include "libheif/heif.h" +#include "libheif/heif_plugin.h" +#include "decoder_openjph.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + + +struct openjph_dec_context +{ + std::vector data; + bool strict_decoding = false; +}; + + +static const int OPENJPH_DEC_PLUGIN_PRIORITY = 100; + +static void openjph_dec_init_plugin() +{ +} + +static void openjph_dec_deinit_plugin() +{ +} + +static int openjph_dec_does_support_format(enum heif_compression_format format) +{ + if (format == heif_compression_HTJ2K) { + return OPENJPH_DEC_PLUGIN_PRIORITY; + } else { + return 0; + } +} + +struct heif_error openjph_dec_new_decoder(void **dec) +{ + struct openjph_dec_context *decoder_context = new openjph_dec_context(); + *dec = decoder_context; + return heif_error_ok; +} + +void openjph_dec_free_decoder(void *decoder_raw) +{ + openjph_dec_context *decoder_context = (openjph_dec_context *)decoder_raw; + delete decoder_context; +} + +void openjph_dec_set_strict_decoding(void *decoder_raw, int flag) +{ + openjph_dec_context *decoder_context = (openjph_dec_context *)decoder_raw; + decoder_context->strict_decoding = flag; +} + +struct heif_error openjph_dec_push_data(void *decoder_raw, const void *frame_data, size_t frame_size) +{ + openjph_dec_context *decoder_context = (openjph_dec_context *)decoder_raw; + const uint8_t* data = (const uint8_t*)frame_data; + decoder_context->data.insert(decoder_context->data.end(), data, data + frame_size); + return heif_error_ok; +} + +struct heif_error openjph_dec_decode_image(void *decoder_raw, struct heif_image **out_img) +{ + openjph_dec_context *decoder_context = (openjph_dec_context *)decoder_raw; + ojph::codestream codestream; + ojph::mem_infile input; + input.open(decoder_context->data.data(), decoder_context->data.size()); + if (!(decoder_context->strict_decoding)) { + codestream.enable_resilience(); + } + codestream.read_headers(&input); + codestream.create(); + + struct heif_image* heif_img = nullptr; + + ojph::param_siz siz = codestream.access_siz(); + ojph::point imageExtent = siz.get_image_extent(); + ojph::point imageOffset = siz.get_image_offset(); + uint32_t width = imageExtent.x - imageOffset.x; + uint32_t height = imageExtent.y - imageOffset.y; + + // TODO: work out colorspace and chroma correctly + heif_colorspace colourspace = heif_colorspace_undefined; + heif_chroma chroma = heif_chroma_undefined; + if (siz.get_num_components() == 3) { + colourspace = heif_colorspace_YCbCr; + chroma = heif_chroma_444; + // check components after the first one + for (unsigned int i = 1; i < siz.get_num_components(); ++i) { + ojph::point downsampling = siz.get_downsampling(i); + if ((downsampling.x == 1) && (downsampling.y == 1)) { + // 4:4:4 + if (chroma != heif_chroma_444) { + struct heif_error err = {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "mismatched chroma format"}; + return err; + } + } else if ((downsampling.x == 2) && (downsampling.y == 1)) { + // 4:2:2 + if ((chroma == heif_chroma_444) && (i == 1)) { + chroma = heif_chroma_422; + } else if (chroma != heif_chroma_422) { + struct heif_error err = {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "mismatched chroma format"}; + return err; + } + } else if ((downsampling.x == 2) && (downsampling.y == 2)) { + // 4:2:0 + if ((chroma == heif_chroma_444) && (i == 1)) { + chroma = heif_chroma_420; + } else if (chroma != heif_chroma_420) { + struct heif_error err = {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "mismatched chroma format"}; + return err; + } + + } + } + } else if (siz.get_num_components() == 1) { + colourspace = heif_colorspace_monochrome; + chroma = heif_chroma_monochrome; + } else { + struct heif_error err = {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "unsupported number of components"}; + return err; + + } + + struct heif_error err = heif_image_create(width, height, colourspace, chroma, &heif_img); + if (err.code != heif_error_Ok) { + assert(heif_img == nullptr); + return err; + } + + // TODO: map component to channel + if (colourspace == heif_colorspace_monochrome) { + heif_image_add_plane(heif_img, heif_channel_Y, width, height, siz.get_bit_depth(0)); + // TODO: make image + struct heif_error err = {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "unsupported monochrome image"}; + return err; + } else { + if (codestream.is_planar()) { + heif_channel channels[] = {heif_channel_Y, heif_channel_Cb, heif_channel_Cr}; + for (uint32_t componentIndex = 0; componentIndex < siz.get_num_components(); ++componentIndex) { + uint32_t component_width = siz.get_recon_width(componentIndex); + uint32_t component_height = siz.get_recon_height(componentIndex); + uint32_t bit_depth = siz.get_bit_depth(componentIndex); + heif_channel channel = channels[componentIndex]; + heif_image_add_plane(heif_img, channel, component_width, component_height, bit_depth); + int planeStride; + uint8_t* plane = heif_image_get_plane(heif_img, channel, &planeStride); + for (uint32_t rowIndex = 0; rowIndex < component_height; rowIndex++) { + uint32_t comp_num; + ojph::line_buf *line = codestream.pull(comp_num); + const int32_t *cursor = line->i32; + for (uint32_t colIndex = 0; colIndex < component_width; ++colIndex) { + int v = *cursor; + // TODO: this only works for the 8 bit case + plane[rowIndex * planeStride + colIndex] = (uint8_t) v; + cursor++; + } + } + } + } else { + struct heif_error err = {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "unsupported interleaved image"}; + return err; + } + } + *out_img = heif_img; + return heif_error_ok; +} + +static const int MAX_PLUGIN_NAME_LENGTH = 80; +static char plugin_name[MAX_PLUGIN_NAME_LENGTH]; + +const char *openjph_dec_plugin_name() +{ + snprintf(plugin_name, MAX_PLUGIN_NAME_LENGTH, + "OpenJPH %s.%s.%s", + OJPH_INT_TO_STRING(OPENJPH_VERSION_MAJOR), + OJPH_INT_TO_STRING(OPENJPH_VERSION_MINOR), + OJPH_INT_TO_STRING(OPENJPH_VERSION_PATCH)); + plugin_name[MAX_PLUGIN_NAME_LENGTH - 1] = 0; + + return plugin_name; +} + +static const struct heif_decoder_plugin decoder_openjph +{ + 3, + openjph_dec_plugin_name, + openjph_dec_init_plugin, + openjph_dec_deinit_plugin, + openjph_dec_does_support_format, + openjph_dec_new_decoder, + openjph_dec_free_decoder, + openjph_dec_push_data, + openjph_dec_decode_image, + openjph_dec_set_strict_decoding, + "openjph" +}; + +const struct heif_decoder_plugin *get_decoder_plugin_openjph() +{ + return &decoder_openjph; +} + +#if PLUGIN_OPENJPH_DECODER +heif_plugin_info plugin_info{ + 1, + heif_plugin_type_decoder, + &decoder_openjph}; +#endif diff --git a/libheif/plugins/decoder_openjph.h b/libheif/plugins/decoder_openjph.h new file mode 100644 index 0000000000..f78442f7de --- /dev/null +++ b/libheif/plugins/decoder_openjph.h @@ -0,0 +1,35 @@ +/* + * OpenJPH High Throughput JPEG 2000 decoder. + * + * 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 . + */ + +#ifndef LIBHEIF_HEIF_DECODER_OPENJPH_H +#define LIBHEIF_HEIF_DECODER_OPENJPH_H + +#include "common_utils.h" + +const struct heif_decoder_plugin* get_decoder_plugin_openjph(); + +#if PLUGIN_OPENJPH_DECODER +extern "C" { +MAYBE_UNUSED LIBHEIF_API extern heif_plugin_info plugin_info; +} +#endif + +#endif diff --git a/libheif/plugins/decoder_vvdec.cc b/libheif/plugins/decoder_vvdec.cc new file mode 100644 index 0000000000..cc573776f8 --- /dev/null +++ b/libheif/plugins/decoder_vvdec.cc @@ -0,0 +1,357 @@ +/* + * AVIF codec. + * Copyright (c) 2019 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_vvdec.h" +#include +#include +#include +#include + +#include + +#if 0 +#include +#include +#endif + + +struct vvdec_decoder +{ + vvdecDecoder* decoder = nullptr; + vvdecAccessUnit* au = nullptr; + + bool strict_decoding = false; + + std::vector> nalus; +}; + +static const char kSuccess[] = "Success"; + +static const int VVDEC_PLUGIN_PRIORITY = 100; + +#define MAX_PLUGIN_NAME_LENGTH 80 + +static char plugin_name[MAX_PLUGIN_NAME_LENGTH]; + + +static const char* vvdec_plugin_name() +{ + const char* version = vvdec_get_version(); + + if (strlen(version) < 60) { + sprintf(plugin_name, "VVCDEC decoder (%s)", version); + } + else { + strcpy(plugin_name, "VVDEC decoder"); + } + + return plugin_name; +} + + +static void vvdec_init_plugin() +{ +} + + +static void vvdec_deinit_plugin() +{ +} + + +static int vvdec_does_support_format(enum heif_compression_format format) +{ + if (format == heif_compression_VVC) { + return VVDEC_PLUGIN_PRIORITY; + } + else { + return 0; + } +} + + +struct heif_error vvdec_new_decoder(void** dec) +{ + auto* decoder = new vvdec_decoder(); + + vvdecParams params; + vvdec_params_default(¶ms); + params.logLevel = VVDEC_INFO; + decoder->decoder = vvdec_decoder_open(¶ms); + + const int MaxNaluSize = 256 * 1024; + decoder->au = vvdec_accessUnit_alloc(); + vvdec_accessUnit_default(decoder->au); + vvdec_accessUnit_alloc_payload(decoder->au, MaxNaluSize); + + *dec = decoder; + + struct heif_error err = {heif_error_Ok, heif_suberror_Unspecified, kSuccess}; + return err; +} + + +void vvdec_free_decoder(void* decoder_raw) +{ + auto* decoder = (vvdec_decoder*) decoder_raw; + + if (!decoder) { + return; + } + + if (decoder->au) { + vvdec_accessUnit_free(decoder->au); + decoder->au = nullptr; + } + + if (decoder->decoder) { + vvdec_decoder_close(decoder->decoder); + decoder->decoder = nullptr; + } + + delete decoder; +} + + +void vvdec_set_strict_decoding(void* decoder_raw, int flag) +{ + auto* decoder = (vvdec_decoder*) decoder_raw; + + decoder->strict_decoding = flag; +} + + +struct heif_error vvdec_push_data(void* decoder_raw, const void* frame_data, size_t frame_size) +{ + auto* decoder = (struct vvdec_decoder*) decoder_raw; + + const auto* data = (const uint8_t*) frame_data; + + for (;;) { + uint32_t size = ((((uint32_t) data[0]) << 24) | + (((uint32_t) data[1]) << 16) | + (((uint32_t) data[2]) << 8) | + (data[3])); + + data += 4; + + std::vector nalu; + nalu.push_back(0); + nalu.push_back(0); + nalu.push_back(1); + nalu.insert(nalu.end(), data, data + size); + + decoder->nalus.push_back(nalu); + data += size; + frame_size -= 4 + size; + if (frame_size == 0) { + break; + } + } + + struct heif_error err = {heif_error_Ok, heif_suberror_Unspecified, kSuccess}; + return err; +} + + +struct heif_error vvdec_decode_image(void* decoder_raw, struct heif_image** out_img) +{ + auto* decoder = (struct vvdec_decoder*) decoder_raw; + + vvdecFrame* frame = nullptr; + + // --- prepare AU payload with maximum NALU size + + size_t max_payload_size = 0; + for (const auto& nalu : decoder->nalus) { + max_payload_size = std::max(max_payload_size, nalu.size()); + } + + if (decoder->au == nullptr || max_payload_size > (size_t) decoder->au->payloadSize) { + if (decoder->au) { + vvdec_accessUnit_free(decoder->au); + } + + decoder->au = vvdec_accessUnit_alloc(); + vvdec_accessUnit_default(decoder->au); + vvdec_accessUnit_alloc_payload(decoder->au, (int)max_payload_size); + } + + // --- feed NALUs into decoder, flush when done + + for (int i = 0;; i++) { + int ret; + + if (i < (int) decoder->nalus.size()) { + const auto& nalu = decoder->nalus[i]; + + memcpy(decoder->au->payload, nalu.data(), nalu.size()); + decoder->au->payloadUsedSize = (int) nalu.size(); + + ret = vvdec_decode(decoder->decoder, decoder->au, &frame); + } + else { + ret = vvdec_flush(decoder->decoder, &frame); + } + + if (ret != VVDEC_OK && ret != VVDEC_EOF && ret != VVDEC_TRY_AGAIN) { + return {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "vvdec decoding error"}; + } + + if (frame) { + break; + } + + if (ret == VVDEC_EOF) { + return {heif_error_Decoder_plugin_error, heif_suberror_Unspecified, "no frame decoded"}; + } + } + + + decoder->nalus.clear(); + + // --- convert decoded frame to heif_image + + heif_chroma chroma; + heif_colorspace colorspace; + + if (frame->colorFormat == VVDEC_CF_YUV400_PLANAR) { + chroma = heif_chroma_monochrome; + colorspace = heif_colorspace_monochrome; + } + else { + if (frame->colorFormat == VVDEC_CF_YUV444_PLANAR) { + chroma = heif_chroma_444; + } + else if (frame->colorFormat == VVDEC_CF_YUV422_PLANAR) { + chroma = heif_chroma_422; + } + else { + chroma = heif_chroma_420; + } + colorspace = heif_colorspace_YCbCr; + } + + struct heif_image* heif_img = nullptr; + struct heif_error err = heif_image_create((int)frame->width, + (int)frame->height, + colorspace, + chroma, + &heif_img); + if (err.code != heif_error_Ok) { + assert(heif_img == nullptr); + return err; + } + + + // --- read nclx parameters from decoded AV1 bitstream + +#if 0 + heif_color_profile_nclx nclx; + nclx.version = 1; + HEIF_WARN_OR_FAIL(decoder->strict_decoding, heif_img, heif_nclx_color_profile_set_color_primaries(&nclx, static_cast(img->cp)), { heif_image_release(heif_img); }); + HEIF_WARN_OR_FAIL(decoder->strict_decoding, heif_img, heif_nclx_color_profile_set_transfer_characteristics(&nclx, static_cast(img->tc)), { heif_image_release(heif_img); }); + HEIF_WARN_OR_FAIL(decoder->strict_decoding, heif_img, heif_nclx_color_profile_set_matrix_coefficients(&nclx, static_cast(img->mc)), { heif_image_release(heif_img); }); + nclx.full_range_flag = (img->range == AOM_CR_FULL_RANGE); + heif_image_set_nclx_color_profile(heif_img, &nclx); +#endif + + // --- transfer data from vvdecFrame to HeifPixelImage + + heif_channel channel2plane[3] = { + heif_channel_Y, + heif_channel_Cb, + heif_channel_Cr + }; + + + int num_planes = (chroma == heif_chroma_monochrome ? 1 : 3); + + for (int c = 0; c < num_planes; c++) { + int bpp = (int)frame->bitDepth; + + const auto& plane = frame->planes[c]; + const uint8_t* data = plane.ptr; + int stride = (int)plane.stride; + + int w = (int)plane.width; + int h = (int)plane.height; + + err = heif_image_add_plane(heif_img, channel2plane[c], w, h, bpp); + if (err.code != heif_error_Ok) { + heif_image_release(heif_img); + return err; + } + + int dst_stride; + uint8_t* dst_mem = heif_image_get_plane(heif_img, channel2plane[c], &dst_stride); + + int bytes_per_pixel = (bpp + 7) / 8; + + 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; + + vvdec_frame_unref(decoder->decoder, frame); + + return err; +} + + +static const struct heif_decoder_plugin decoder_vvdec + { + 3, + vvdec_plugin_name, + vvdec_init_plugin, + vvdec_deinit_plugin, + vvdec_does_support_format, + vvdec_new_decoder, + vvdec_free_decoder, + vvdec_push_data, + vvdec_decode_image, + vvdec_set_strict_decoding, + "vvdec" + }; + + +const struct heif_decoder_plugin* get_decoder_plugin_vvdec() +{ + return &decoder_vvdec; +} + + +#if PLUGIN_VVDEC +heif_plugin_info plugin_info { + 1, + heif_plugin_type_decoder, + &decoder_vvdec +}; +#endif diff --git a/libheif/metadata_compression.h b/libheif/plugins/decoder_vvdec.h similarity index 64% rename from libheif/metadata_compression.h rename to libheif/plugins/decoder_vvdec.h index 3780620b92..15f01df2c6 100644 --- a/libheif/metadata_compression.h +++ b/libheif/plugins/decoder_vvdec.h @@ -1,6 +1,6 @@ /* * HEIF codec. - * Copyright (c) 2022 Dirk Farin + * Copyright (c) 2017 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_VVDEC_H +#define LIBHEIF_DECODER_VVDEC_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_vvdec(); + +#if PLUGIN_VVDEC +extern "C" { +MAYBE_UNUSED LIBHEIF_API extern heif_plugin_info plugin_info; +} #endif -#endif //LIBHEIF_METADATA_COMPRESSION_H +#endif diff --git a/libheif/plugins/encoder_aom.cc b/libheif/plugins/encoder_aom.cc index a36e0c540b..849f8f3d35 100644 --- a/libheif/plugins/encoder_aom.cc +++ b/libheif/plugins/encoder_aom.cc @@ -20,12 +20,13 @@ #include "libheif/heif.h" #include "libheif/heif_plugin.h" -#include "libheif/common_utils.h" +#include "common_utils.h" #include #include #include #include #include +#include #include #include "encoder_aom.h" @@ -237,11 +238,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++; diff --git a/libheif/plugins/encoder_aom.h b/libheif/plugins/encoder_aom.h index 5bd1d3183f..f1717e3588 100644 --- a/libheif/plugins/encoder_aom.h +++ b/libheif/plugins/encoder_aom.h @@ -21,7 +21,7 @@ #ifndef LIBHEIF_ENCODER_AOM_H #define LIBHEIF_ENCODER_AOM_H -#include "libheif/common_utils.h" +#include "common_utils.h" const struct heif_encoder_plugin* get_encoder_plugin_aom(); diff --git a/libheif/plugins/encoder_jpeg.h b/libheif/plugins/encoder_jpeg.h index cdd7248934..bd59aa660d 100644 --- a/libheif/plugins/encoder_jpeg.h +++ b/libheif/plugins/encoder_jpeg.h @@ -21,7 +21,7 @@ #ifndef LIBHEIF_ENCODER_JPEG_H #define LIBHEIF_ENCODER_JPEG_H -#include "libheif/common_utils.h" +#include "common_utils.h" const struct heif_encoder_plugin* get_encoder_plugin_jpeg(); diff --git a/libheif/plugins/encoder_kvazaar.h b/libheif/plugins/encoder_kvazaar.h index e0a98c5e48..772d4419b6 100644 --- a/libheif/plugins/encoder_kvazaar.h +++ b/libheif/plugins/encoder_kvazaar.h @@ -21,7 +21,7 @@ #ifndef LIBHEIF_ENCODER_KVAZAAR_H #define LIBHEIF_ENCODER_KVAZAAR_H -#include "libheif/common_utils.h" +#include "common_utils.h" /* TODO: check whether this is also the case with kvazaar. * diff --git a/libheif/plugins/encoder_mask.h b/libheif/plugins/encoder_mask.h index eb769f6580..0a55857acf 100644 --- a/libheif/plugins/encoder_mask.h +++ b/libheif/plugins/encoder_mask.h @@ -21,7 +21,7 @@ #ifndef LIBHEIF_ENCODER_MASK_H #define LIBHEIF_ENCODER_MASK_H -#include "libheif/common_utils.h" +#include "common_utils.h" // This is a dummy module. It does not actually do anything except parameter parsing. // The actual codec is included in the library. diff --git a/libheif/plugins/encoder_openjpeg.h b/libheif/plugins/encoder_openjpeg.h index 55dcfb8aad..1f2828ca8a 100644 --- a/libheif/plugins/encoder_openjpeg.h +++ b/libheif/plugins/encoder_openjpeg.h @@ -22,7 +22,7 @@ #ifndef LIBHEIF_ENCODER_OPENJPEG_H #define LIBHEIF_ENCODER_OPENJPEG_H -#include "libheif/common_utils.h" +#include "common_utils.h" const struct heif_encoder_plugin* get_encoder_plugin_openjpeg(); diff --git a/libheif/plugins/encoder_openjph.h b/libheif/plugins/encoder_openjph.h index 1b8002834b..25e77e5719 100644 --- a/libheif/plugins/encoder_openjph.h +++ b/libheif/plugins/encoder_openjph.h @@ -23,7 +23,7 @@ #ifndef LIBHEIF_ENCODER_OPENJPH_H #define LIBHEIF_ENCODER_OPENJPH_H -#include "libheif/common_utils.h" +#include "common_utils.h" const struct heif_encoder_plugin* get_encoder_plugin_openjph(); diff --git a/libheif/plugins/encoder_rav1e.h b/libheif/plugins/encoder_rav1e.h index d570f02926..bb0eb9c915 100644 --- a/libheif/plugins/encoder_rav1e.h +++ b/libheif/plugins/encoder_rav1e.h @@ -21,7 +21,7 @@ #ifndef LIBHEIF_ENCODER_RAV1E_H #define LIBHEIF_ENCODER_RAV1E_H -#include "libheif/common_utils.h" +#include "common_utils.h" const struct heif_encoder_plugin* get_encoder_plugin_rav1e(); diff --git a/libheif/plugins/encoder_svt.h b/libheif/plugins/encoder_svt.h index 42c820a748..461b575bf0 100644 --- a/libheif/plugins/encoder_svt.h +++ b/libheif/plugins/encoder_svt.h @@ -21,7 +21,7 @@ #ifndef LIBHEIF_ENCODER_SVT_H #define LIBHEIF_ENCODER_SVT_H -#include "libheif/common_utils.h" +#include "common_utils.h" const struct heif_encoder_plugin* get_encoder_plugin_svt(); diff --git a/libheif/plugins/encoder_uncompressed.h b/libheif/plugins/encoder_uncompressed.h index e2bba2b5db..4cfc59986f 100644 --- a/libheif/plugins/encoder_uncompressed.h +++ b/libheif/plugins/encoder_uncompressed.h @@ -21,7 +21,7 @@ #ifndef LIBHEIF_ENCODER_UNCOMPRESSED_H #define LIBHEIF_ENCODER_UNCOMPRESSED_H -#include "libheif/common_utils.h" +#include "common_utils.h" // This is a dummy module. It does not actually do anything except parameter parsing. // The actual codec is included in the library. diff --git a/libheif/plugins/encoder_uvg266.cc b/libheif/plugins/encoder_uvg266.cc index 101743728d..80864188c5 100644 --- a/libheif/plugins/encoder_uvg266.cc +++ b/libheif/plugins/encoder_uvg266.cc @@ -32,9 +32,6 @@ extern "C" { #include } -#include -#include - static const char* kError_unspecified_error = "Unspecified encoder error"; static const char* kError_unsupported_bit_depth = "Bit depth not supported by uvg266"; @@ -51,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_uvg266.h b/libheif/plugins/encoder_uvg266.h index b9eec90c25..61204c8e5c 100644 --- a/libheif/plugins/encoder_uvg266.h +++ b/libheif/plugins/encoder_uvg266.h @@ -21,7 +21,7 @@ #ifndef LIBHEIF_ENCODER_UVG266_H #define LIBHEIF_ENCODER_UVG266_H -#include "libheif/common_utils.h" +#include "common_utils.h" /* TODO: check whether this is also the case with uvg266. * diff --git a/libheif/plugins/encoder_vvenc.cc b/libheif/plugins/encoder_vvenc.cc new file mode 100644 index 0000000000..792c1780a7 --- /dev/null +++ b/libheif/plugins/encoder_vvenc.cc @@ -0,0 +1,716 @@ +/* + * 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 +} + + +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 = 32; + p->has_default = true; + p->integer.have_minimum_maximum = true; + p->integer.minimum = 0; + p->integer.maximum = 63; + 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; + + 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.h b/libheif/plugins/encoder_x265.h index f9dbdf20f6..4869280e23 100644 --- a/libheif/plugins/encoder_x265.h +++ b/libheif/plugins/encoder_x265.h @@ -21,7 +21,7 @@ #ifndef LIBHEIF_ENCODER_X265_H #define LIBHEIF_ENCODER_X265_H -#include "libheif/common_utils.h" +#include "common_utils.h" /* Image sizes in HEVC: since HEVC does not allow for odd image dimensions when using chroma 4:2:0, our strategy is as follows. diff --git a/libheif/plugins_unix.cc b/libheif/plugins_unix.cc index 98687a13e6..e7ca9e6666 100644 --- a/libheif/plugins_unix.cc +++ b/libheif/plugins_unix.cc @@ -20,7 +20,7 @@ #include "plugins_unix.h" -#include "heif_plugin.h" +#include "libheif/heif_plugin.h" #include #include diff --git a/libheif/plugins_windows.cc b/libheif/plugins_windows.cc index ad0e8ae3b6..b11cec9440 100644 --- a/libheif/plugins_windows.cc +++ b/libheif/plugins_windows.cc @@ -20,7 +20,7 @@ #include "plugins_windows.h" -#include "heif_plugin.h" +#include "libheif/heif_plugin.h" #include diff --git a/libheif/security_limits.h b/libheif/security_limits.h index f1c177c0a1..302138127f 100644 --- a/libheif/security_limits.h +++ b/libheif/security_limits.h @@ -31,8 +31,7 @@ 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; +static const int64_t MAX_IMAGE_SIZE = 32768 * 32768; // Maximum nesting level of boxes in input files. // We put a limit on this to avoid unlimited stack usage by malicious input files. diff --git a/libheif/uncompressed_box.cc b/libheif/uncompressed_box.cc deleted file mode 100644 index 0020225b54..0000000000 --- a/libheif/uncompressed_box.cc +++ /dev/null @@ -1,332 +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 "libheif/heif.h" -#include "uncompressed.h" -#include "uncompressed_box.h" - - -/** - * Check for valid component format. - * - * @param format the format value to check - * @return true if the format is a valid value, or false otherwise - */ -bool is_valid_component_format(uint8_t format) -{ - return format <= component_format_max_valid; -} - -static std::map sNames_uncompressed_component_format{ - {component_format_unsigned, "unsigned"}, - {component_format_float, "float"}, - {component_format_complex, "complex"} -}; - - -/** - * Check for valid interleave mode. - * - * @param interleave the interleave value to check - * @return true if the interleave mode is valid, or false otherwise - */ -bool is_valid_interleave_mode(uint8_t interleave) -{ - return interleave <= interleave_mode_max_valid; -} - -static std::map sNames_uncompressed_interleave_mode{ - {interleave_mode_component, "component"}, - {interleave_mode_pixel, "pixel"}, - {interleave_mode_mixed, "mixed"}, - {interleave_mode_row, "row"}, - {interleave_mode_tile_component, "tile-component"}, - {interleave_mode_multi_y, "multi-y"} -}; - - -/** - * Check for valid sampling mode. - * - * @param sampling the sampling value to check - * @return true if the sampling mode is valid, or false otherwise - */ -bool is_valid_sampling_mode(uint8_t sampling) -{ - return sampling <= sampling_mode_max_valid; -} - -static std::map sNames_uncompressed_sampling_mode{ - {sampling_mode_no_subsampling, "no subsampling"}, - {sampling_mode_422, "4:2:2"}, - {sampling_mode_420, "4:2:0"}, - {sampling_mode_411, "4:1:1"} -}; - - -bool is_predefined_component_type(uint16_t type) -{ - // check whether the component type can be mapped to heif_uncompressed_component_type and we have a name defined for - // it in sNames_uncompressed_component_type. - return (type >= 0 && type <= component_type_max_valid); -} - -static std::map sNames_uncompressed_component_type{ - {component_type_monochrome, "monochrome"}, - {component_type_Y, "Y"}, - {component_type_Cb, "Cb"}, - {component_type_Cr, "Cr"}, - {component_type_red, "red"}, - {component_type_green, "green"}, - {component_type_blue, "blue"}, - {component_type_alpha, "alpha"}, - {component_type_depth, "depth"}, - {component_type_disparity, "disparity"}, - {component_type_palette, "palette"}, - {component_type_filter_array, "filter-array"}, - {component_type_padded, "padded"}, - {component_type_cyan, "cyan"}, - {component_type_magenta, "magenta"}, - {component_type_yellow, "yellow"}, - {component_type_key_black, "key (black)"} -}; - -template const char* get_name(T val, const std::map& table) -{ - auto iter = table.find(val); - if (iter == table.end()) { - return "unknown"; - } - else { - return iter->second; - } -} - -Error Box_cmpd::parse(BitstreamRange& range) -{ - unsigned int component_count = range.read32(); - - for (unsigned int i = 0; i < component_count && !range.error() && !range.eof(); i++) { - Component component; - component.component_type = range.read16(); - if (component.component_type >= 0x8000) { - component.component_type_uri = range.read_string(); - } - else { - component.component_type_uri = std::string(); - } - m_components.push_back(component); - } - - return range.get_error(); -} - -std::string Box_cmpd::Component::get_component_type_name(uint16_t component_type) -{ - std::stringstream sstr; - - if (is_predefined_component_type(component_type)) { - sstr << get_name(heif_uncompressed_component_type(component_type), sNames_uncompressed_component_type) << "\n"; - } - else { - sstr << "0x" << std::hex << component_type << std::dec << "\n"; - } - - return sstr.str(); -} - - -std::string Box_cmpd::dump(Indent& indent) const -{ - std::ostringstream sstr; - sstr << Box::dump(indent); - - for (const auto& component : m_components) { - sstr << indent << "component_type: " << component.get_component_type_name(); - - if (component.component_type >= 0x8000) { - sstr << indent << "| component_type_uri: " << component.component_type_uri << "\n"; - } - } - - return sstr.str(); -} - -Error Box_cmpd::write(StreamWriter& writer) const -{ - size_t box_start = reserve_box_header_space(writer); - - writer.write32((uint32_t) m_components.size()); - for (const auto& component : m_components) { - writer.write16(component.component_type); - if (component.component_type >= 0x8000) { - writer.write(component.component_type_uri); - } - } - - prepend_header(writer, box_start); - - return Error::Ok; -} - -Error Box_uncC::parse(BitstreamRange& range) -{ - parse_full_box_header(range); - m_profile = range.read32(); - if (get_version() != 0) { - return Error{heif_error_Invalid_input, heif_suberror_Unsupported_data_version, "Unsupported version (only 0 is currently supported)"}; - } - - unsigned int component_count = range.read32(); - - for (unsigned int i = 0; i < component_count && !range.error() && !range.eof(); i++) { - Component component; - component.component_index = range.read16(); - component.component_bit_depth = uint16_t(range.read8() + 1); - component.component_format = range.read8(); - component.component_align_size = range.read8(); - m_components.push_back(component); - - if (!is_valid_component_format(component.component_format)) { - return Error{heif_error_Invalid_input, heif_suberror_Invalid_parameter_value, "Invalid component format"}; - } - } - - m_sampling_type = range.read8(); - if (!is_valid_sampling_mode(m_sampling_type)) { - return Error{heif_error_Invalid_input, heif_suberror_Invalid_parameter_value, "Invalid sampling mode"}; - } - - m_interleave_type = range.read8(); - if (!is_valid_interleave_mode(m_interleave_type)) { - return Error{heif_error_Invalid_input, heif_suberror_Invalid_parameter_value, "Invalid interleave mode"}; - } - - m_block_size = range.read8(); - - uint8_t flags = range.read8(); - m_components_little_endian = !!(flags & 0x80); - m_block_pad_lsb = !!(flags & 0x40); - m_block_little_endian = !!(flags & 0x20); - m_block_reversed = !!(flags & 0x10); - m_pad_unknown = !!(flags & 0x08); - - m_pixel_size = range.read32(); - - m_row_align_size = range.read32(); - - m_tile_align_size = range.read32(); - - m_num_tile_cols = range.read32() + 1; - - m_num_tile_rows = range.read32() + 1; - - return range.get_error(); -} - - -std::string Box_uncC::dump(Indent& indent) const -{ - std::ostringstream sstr; - sstr << Box::dump(indent); - - sstr << indent << "profile: " << m_profile; - if (m_profile != 0) { - sstr << " (" << to_fourcc(m_profile) << ")"; - } - sstr << "\n"; - - for (const auto& component : m_components) { - sstr << indent << "component_index: " << component.component_index << "\n"; - 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"; - } - - sstr << indent << "sampling_type: " << get_name(heif_uncompressed_sampling_mode(m_sampling_type), sNames_uncompressed_sampling_mode) << "\n"; - - sstr << indent << "interleave_type: " << get_name(heif_uncompressed_interleave_mode(m_interleave_type), sNames_uncompressed_interleave_mode) << "\n"; - - sstr << indent << "block_size: " << (int) m_block_size << "\n"; - - sstr << indent << "components_little_endian: " << m_components_little_endian << "\n"; - sstr << indent << "block_pad_lsb: " << m_block_pad_lsb << "\n"; - sstr << indent << "block_little_endian: " << m_block_little_endian << "\n"; - sstr << indent << "block_reversed: " << m_block_reversed << "\n"; - sstr << indent << "pad_unknown: " << m_pad_unknown << "\n"; - - sstr << indent << "pixel_size: " << m_pixel_size << "\n"; - - sstr << indent << "row_align_size: " << m_row_align_size << "\n"; - - sstr << indent << "tile_align_size: " << m_tile_align_size << "\n"; - - sstr << indent << "num_tile_cols: " << m_num_tile_cols << "\n"; - - sstr << indent << "num_tile_rows: " << m_num_tile_rows << "\n"; - - return sstr.str(); -} - - -Error Box_uncC::write(StreamWriter& writer) const -{ - size_t box_start = reserve_box_header_space(writer); - - writer.write32(m_profile); - writer.write32((uint32_t) m_components.size()); - for (const auto& component : m_components) { - if (component.component_bit_depth < 1 || component.component_bit_depth > 256) { - return {heif_error_Invalid_input, heif_suberror_Invalid_parameter_value, "component bit-depth out of range [1..256]"}; - } - - writer.write16(component.component_index); - writer.write8(uint8_t(component.component_bit_depth - 1)); - writer.write8(component.component_format); - writer.write8(component.component_align_size); - } - writer.write8(m_sampling_type); - writer.write8(m_interleave_type); - writer.write8(m_block_size); - uint8_t flags = 0; - flags |= (m_components_little_endian ? 0x80 : 0); - flags |= (m_block_pad_lsb ? 0x40 : 0); - flags |= (m_block_little_endian ? 0x20 : 0); - flags |= (m_block_reversed ? 0x10 : 0); - flags |= (m_pad_unknown ? 0x08 : 0); - writer.write8(flags); - writer.write32(m_pixel_size); - writer.write32(m_row_align_size); - writer.write32(m_tile_align_size); - writer.write32(m_num_tile_cols - 1); - writer.write32(m_num_tile_rows - 1); - prepend_header(writer, box_start); - - return Error::Ok; -} - diff --git a/scripts/check-emscripten-enums.sh b/scripts/check-emscripten-enums.sh index 5c2d5486c6..aec939d22e 100755 --- a/scripts/check-emscripten-enums.sh +++ b/scripts/check-emscripten-enums.sh @@ -31,14 +31,14 @@ DEFINE_TYPES=" API_DEFINES="" for type in $DEFINE_TYPES; do - DEFINES=$(grep "^[ \t]*$type" libheif/heif.h | sed 's|[[:space:]]*\([^ \t=]*\)[[:space:]]*=.*|\1|g') + DEFINES=$(grep "^[ \t]*$type" libheif/api/libheif/heif.h | sed 's|[[:space:]]*\([^ \t=]*\)[[:space:]]*=.*|\1|g') if [ -z "$API_DEFINES" ]; then API_DEFINES="$DEFINES" else API_DEFINES="$API_DEFINES $DEFINES" fi - ALIASES=$(grep "^[ \t]*#define $type" libheif/heif.h | sed 's|[[:space:]]*#define \([^ \t]*\)[[:space:]]*.*|\1|g') + ALIASES=$(grep "^[ \t]*#define $type" libheif/api/libheif/heif.h | sed 's|[[:space:]]*#define \([^ \t]*\)[[:space:]]*.*|\1|g') if [ ! -z "$ALIASES" ]; then API_DEFINES="$API_DEFINES $ALIASES" @@ -48,7 +48,7 @@ API_DEFINES=$(echo "$API_DEFINES" | sort) EMSCRIPTEN_DEFINES="" for type in $DEFINE_TYPES; do - DEFINES=$(grep "\.value(\"$type" libheif/heif_emscripten.h | sed 's|[^\"]*\"\(.*\)\".*|\1|g') + DEFINES=$(grep "\.value(\"$type" libheif/api/libheif/heif_emscripten.h | sed 's|[^\"]*\"\(.*\)\".*|\1|g') if [ -z "$EMSCRIPTEN_DEFINES" ]; then EMSCRIPTEN_DEFINES="$DEFINES" else diff --git a/scripts/check-go-enums.sh b/scripts/check-go-enums.sh index d77f3e7576..06270daf0c 100755 --- a/scripts/check-go-enums.sh +++ b/scripts/check-go-enums.sh @@ -31,14 +31,14 @@ DEFINE_TYPES=" API_DEFINES="" for type in $DEFINE_TYPES; do - DEFINES=$(grep "^[ \t]*$type" libheif/heif.h | sed 's|[[:space:]]*\([^ \t=]*\)[[:space:]]*=.*|\1|g') + DEFINES=$(grep "^[ \t]*$type" libheif/api/libheif/heif.h | sed 's|[[:space:]]*\([^ \t=]*\)[[:space:]]*=.*|\1|g') if [ -z "$API_DEFINES" ]; then API_DEFINES="$DEFINES" else API_DEFINES="$API_DEFINES $DEFINES" fi - ALIASES=$(grep "^[ \t]*#define $type" libheif/heif.h | sed 's|[[:space:]]*#define \([^ \t]*\)[[:space:]]*.*|\1|g') + ALIASES=$(grep "^[ \t]*#define $type" libheif/api/libheif/heif.h | sed 's|[[:space:]]*#define \([^ \t]*\)[[:space:]]*.*|\1|g') if [ ! -z "$ALIASES" ]; then API_DEFINES="$API_DEFINES $ALIASES" 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 f28e753055..b2a5b267e5 100755 --- a/scripts/run-ci.sh +++ b/scripts/run-ci.sh @@ -163,19 +163,19 @@ if [ -z "$EMSCRIPTEN_VERSION" ] && [ -z "$CHECK_LICENSES" ] && [ -z "$TARBALL" ] ${BIN_WRAPPER} ./examples/heif-enc${BIN_SUFFIX} --list-encoders echo "List available decoders" - ${BIN_WRAPPER} ./examples/heif-convert${BIN_SUFFIX} --list-decoders + ${BIN_WRAPPER} ./examples/heif-dec${BIN_SUFFIX} --list-decoders echo "Dumping information of sample file ..." ${BIN_WRAPPER} ./examples/heif-info${BIN_SUFFIX} --dump-boxes examples/example.heic if [ ! -z "$WITH_GRAPHICS" ] && [ ! -z "$WITH_HEIF_DECODER" ]; then echo "Converting sample HEIF file to JPEG ..." - ${BIN_WRAPPER} ./examples/heif-convert${BIN_SUFFIX} examples/example.heic example.jpg + ${BIN_WRAPPER} ./examples/heif-dec${BIN_SUFFIX} examples/example.heic example.jpg echo "Checking first generated file ..." [ -s "example-1.jpg" ] || exit 1 echo "Checking second generated file ..." [ -s "example-2.jpg" ] || exit 1 echo "Converting sample HEIF file to PNG ..." - ${BIN_WRAPPER} ./examples/heif-convert${BIN_SUFFIX} examples/example.heic example.png + ${BIN_WRAPPER} ./examples/heif-dec${BIN_SUFFIX} examples/example.heic example.png echo "Checking first generated file ..." [ -s "example-1.png" ] || exit 1 echo "Checking second generated file ..." @@ -183,11 +183,11 @@ if [ -z "$EMSCRIPTEN_VERSION" ] && [ -z "$CHECK_LICENSES" ] && [ -z "$TARBALL" ] fi if [ ! -z "$WITH_GRAPHICS" ] && [ ! -z "$WITH_AVIF_DECODER" ]; then echo "Converting sample AVIF file to JPEG ..." - ${BIN_WRAPPER} ./examples/heif-convert${BIN_SUFFIX} examples/example.avif example.jpg + ${BIN_WRAPPER} ./examples/heif-dec${BIN_SUFFIX} examples/example.avif example.jpg echo "Checking generated file ..." [ -s "example.jpg" ] || exit 1 echo "Converting sample AVIF file to PNG ..." - ${BIN_WRAPPER} ./examples/heif-convert${BIN_SUFFIX} examples/example.avif example.png + ${BIN_WRAPPER} ./examples/heif-dec${BIN_SUFFIX} examples/example.avif example.png echo "Checking generated file ..." [ -s "example.png" ] || exit 1 fi @@ -197,14 +197,14 @@ if [ -z "$EMSCRIPTEN_VERSION" ] && [ -z "$CHECK_LICENSES" ] && [ -z "$TARBALL" ] echo "Checking generated file ..." [ -s "output-single.heic" ] || exit 1 echo "Converting back generated heif to JPEG ..." - ${BIN_WRAPPER} ./examples/heif-convert${BIN_SUFFIX} output-single.heic output-single.jpg + ${BIN_WRAPPER} ./examples/heif-dec${BIN_SUFFIX} output-single.heic output-single.jpg echo "Checking generated file ..." [ -s "output-single.jpg" ] || exit 1 echo "Converting multiple JPEG files to heif ..." ${BIN_WRAPPER} ./examples/heif-enc${BIN_SUFFIX} -o output-multi.heic --verbose --verbose --verbose --thumb 320x240 example-1.jpg example-2.jpg echo "Checking generated file ..." [ -s "output-multi.heic" ] || exit 1 - ${BIN_WRAPPER} ./examples/heif-convert${BIN_SUFFIX} output-multi.heic output-multi.jpg + ${BIN_WRAPPER} ./examples/heif-dec${BIN_SUFFIX} output-multi.heic output-multi.jpg echo "Checking first generated file ..." [ -s "output-multi-1.jpg" ] || exit 1 echo "Checking second generated file ..." @@ -216,7 +216,7 @@ if [ -z "$EMSCRIPTEN_VERSION" ] && [ -z "$CHECK_LICENSES" ] && [ -z "$TARBALL" ] echo "Checking generated file ..." [ -s "output-jpeg.avif" ] || exit 1 echo "Converting back generated AVIF to JPEG ..." - ${BIN_WRAPPER} ./examples/heif-convert${BIN_SUFFIX} output-jpeg.avif output-jpeg.jpg + ${BIN_WRAPPER} ./examples/heif-dec${BIN_SUFFIX} output-jpeg.avif output-jpeg.jpg echo "Checking generated file ..." [ -s "output-jpeg.jpg" ] || exit 1 fi diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index ae3869b723..5265110d65 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,5 +1,5 @@ # Needed to find libheif/heif_version.h while compiling the library -include_directories(${libheif_BINARY_DIR} ${libheif_SOURCE_DIR}) +include_directories(${libheif_BINARY_DIR} ${libheif_SOURCE_DIR}/libheif ${libheif_SOURCE_DIR}/libheif/api) # prepare C++ configuration file (test-config.cc) @@ -20,15 +20,20 @@ if (WITH_REDUCED_VISIBILITY) else() add_libheif_test(box_equals) add_libheif_test(conversion) + add_libheif_test(icbr_box) add_libheif_test(idat) add_libheif_test(jpeg2000) add_libheif_test(avc_box) +endif() + +if (NOT WITH_REDUCED_VISIBILITY AND WITH_UNCOMPRESSED_CODEC) add_libheif_test(uncompressed_box) 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) @@ -45,6 +50,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) diff --git a/tests/avc_box.cc b/tests/avc_box.cc index c9165de8d0..4a2fd7bc3b 100644 --- a/tests/avc_box.cc +++ b/tests/avc_box.cc @@ -25,8 +25,8 @@ */ #include "catch.hpp" -#include "libheif/avc.h" -#include "libheif/error.h" +#include "codecs/avc.h" +#include "error.h" #include #include #include diff --git a/tests/box_equals.cc b/tests/box_equals.cc index 1cfb596320..a9ee6c9c82 100644 --- a/tests/box_equals.cc +++ b/tests/box_equals.cc @@ -25,7 +25,7 @@ */ #include "catch.hpp" -#include "libheif/box.h" +#include "box.h" TEST_CASE("box equals") { std::shared_ptr ispe1 = std::make_shared(); diff --git a/tests/conversion.cc b/tests/conversion.cc index 86d96da6fc..3ca71419bd 100644 --- a/tests/conversion.cc +++ b/tests/conversion.cc @@ -26,8 +26,8 @@ #include #include "catch.hpp" -#include "libheif/color-conversion/colorconversion.h" -#include "libheif/pixelimage.h" +#include "color-conversion/colorconversion.h" +#include "pixelimage.h" // Enable for more verbose test output. constexpr bool kEnableDebugOutput = false; diff --git a/tests/data/rgb_generic_compressed_brotli.heif b/tests/data/rgb_generic_compressed_brotli.heif new file mode 100644 index 0000000000..8771ddc811 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..acb484a43c 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..86c2d3e5e6 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..98455c992d 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..c0507f6cd9 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..4b905aa465 Binary files /dev/null and b/tests/data/rgb_generic_compressed_zlib_tiled.heif differ diff --git a/tests/encode.cc b/tests/encode.cc index 48b83ee889..d6adff5bb8 100644 --- a/tests/encode.cc +++ b/tests/encode.cc @@ -26,7 +26,7 @@ #include "catch.hpp" #include "libheif/heif.h" -#include "libheif/pixelimage.h" +#include "pixelimage.h" #include "libheif/api_structs.h" #include diff --git a/tests/encode_htj2k.cc b/tests/encode_htj2k.cc index 9214c7031e..98c84efd4d 100644 --- a/tests/encode_htj2k.cc +++ b/tests/encode_htj2k.cc @@ -26,8 +26,8 @@ #include "catch.hpp" #include "libheif/heif.h" -#include "libheif/pixelimage.h" #include "libheif/api_structs.h" +#include "pixelimage.h" #include "test_utils.h" diff --git a/tests/encode_jpeg2000.cc b/tests/encode_jpeg2000.cc index bb2c7cb2e0..1acf532be5 100644 --- a/tests/encode_jpeg2000.cc +++ b/tests/encode_jpeg2000.cc @@ -26,8 +26,8 @@ #include "catch.hpp" #include "libheif/heif.h" -#include "libheif/pixelimage.h" #include "libheif/api_structs.h" +#include "pixelimage.h" #include "test_utils.h" diff --git a/tests/extended_type.cc b/tests/extended_type.cc new file mode 100644 index 0000000000..eacab47565 --- /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_extended_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/icbr_box.cc b/tests/icbr_box.cc new file mode 100644 index 0000000000..29f2d8b02b --- /dev/null +++ b/tests/icbr_box.cc @@ -0,0 +1,51 @@ +/* + libheif icbr 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 "box.h" +#include +#include + +TEST_CASE("icbr bad") { + std::vector testData{0x00, 0x00, 0x00, 0x00, 'i', 'c', 'b', 'r', + 0x00, 0x11, 0x69, 0x6c, 0x6f, 0x9d, 0xf4, 0x89,}; + auto reader = std::make_shared(testData.data(), + testData.size(), false); + + BitstreamRange range(reader, testData.size()); + for (;;) { + std::shared_ptr box; + Error error = Box::read(range, &box); + if (error != Error::Ok || range.error()) { + break; + } + + box->get_type(); + box->get_type_string(); + Indent indent; + box->dump(indent); + } +} diff --git a/tests/idat.cc b/tests/idat.cc index f03721b1b7..7782516891 100644 --- a/tests/idat.cc +++ b/tests/idat.cc @@ -25,7 +25,7 @@ */ #include "catch.hpp" -#include "libheif/box.h" +#include "box.h" #include #include diff --git a/tests/jpeg2000.cc b/tests/jpeg2000.cc index d6a1155711..f528a5ddcc 100644 --- a/tests/jpeg2000.cc +++ b/tests/jpeg2000.cc @@ -26,7 +26,7 @@ #include "catch.hpp" #include "libheif/heif.h" -#include "libheif/jpeg2000.h" +#include "codecs/jpeg2000.h" #include #include diff --git a/tests/uncompressed_box.cc b/tests/uncompressed_box.cc index 0b6a2b464d..8b2205b027 100644 --- a/tests/uncompressed_box.cc +++ b/tests/uncompressed_box.cc @@ -25,10 +25,10 @@ */ #include "catch.hpp" -#include "libheif/box.h" +#include "box.h" #include "libheif/heif.h" -#include "libheif/uncompressed.h" -#include "libheif/uncompressed_box.h" +#include "codecs/uncompressed.h" +#include "codecs/uncompressed_box.h" #include #include @@ -216,3 +216,352 @@ TEST_CASE( "uncC" ) 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"); } + +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); + 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); + 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); + 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); + 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); + 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_must_decompress_individual_entities() == false); + REQUIRE(cmpC->get_compressed_range_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\nmust_decompress_individual_entities: 0\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', + 0x82 + }; + + 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); + 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_must_decompress_individual_entities() == true); + REQUIRE(cmpC->get_compressed_range_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\nmust_decompress_individual_entities: 1\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', + 0x81 + }; + + 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); + 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_must_decompress_individual_entities() == true); + REQUIRE(cmpC->get_compressed_range_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\nmust_decompress_individual_entities: 1\ncompressed_entity_type: 1\n"); + + } + +TEST_CASE("icbr_empty") { + std::vector byteArray{ + 0x00, 0x00, 0x00, 0x10, 'i', 'c', 'b', 'r', + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 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); + REQUIRE(error == Error::Ok); + REQUIRE(range.error() == 0); + + REQUIRE(box->get_short_type() == fourcc("icbr")); + REQUIRE(box->get_type_string() == "icbr"); + std::shared_ptr icbr = std::dynamic_pointer_cast(box); + REQUIRE(icbr != nullptr); + REQUIRE(icbr->get_ranges().size() == 0); + + StreamWriter writer; + Error err = icbr->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 = icbr->dump(indent); + REQUIRE(dump_output == "Box: icbr -----\nsize: 16 (header size: 12)\nnum_ranges: 0\n"); +} + +TEST_CASE("icbr_version0") { + std::vector byteArray{ + 0x00, 0x00, 0x00, 0x20, 'i', 'c', 'b', 'r', + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x0a, 0x03, 0x00, 0x01, 0x02, 0x03, + 0x00, 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); + REQUIRE(error == Error::Ok); + REQUIRE(range.error() == 0); + + REQUIRE(box->get_short_type() == fourcc("icbr")); + REQUIRE(box->get_type_string() == "icbr"); + std::shared_ptr icbr = std::dynamic_pointer_cast(box); + REQUIRE(icbr != nullptr); + REQUIRE(icbr->get_ranges().size() == 2); + REQUIRE(icbr->get_version() == 0); + + StreamWriter writer; + Error err = icbr->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 = icbr->dump(indent); + REQUIRE(dump_output == "Box: icbr -----\nsize: 32 (header size: 12)\nnum_ranges: 2\nrange_offset: 2563, range_size: 66051\nrange_offset: 131850, range_size: 263431\n"); +} + +TEST_CASE("icbr_version1") { + std::vector byteArray{ + 0x00, 0x00, 0x00, 0x30, 'i', 'c', 'b', 'r', + 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0a, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x02, 0x03, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x03, 0x0a, + 0x00, 0x00, 0x00, 0x00, 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); + REQUIRE(error == Error::Ok); + REQUIRE(range.error() == 0); + + REQUIRE(box->get_short_type() == fourcc("icbr")); + REQUIRE(box->get_type_string() == "icbr"); + std::shared_ptr icbr = std::dynamic_pointer_cast(box); + REQUIRE(icbr != nullptr); + REQUIRE(icbr->get_ranges().size() == 2); + REQUIRE(icbr->get_version() == 1); + + StreamWriter writer; + Error err = icbr->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 = icbr->dump(indent); + REQUIRE(dump_output == "Box: icbr -----\nsize: 48 (header size: 12)\nnum_ranges: 2\nrange_offset: 2563, range_size: 66051\nrange_offset: 131850, range_size: 263431\n"); +} 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 fbec7d2376..b3326dc4d4 100644 --- a/tests/uncompressed_encode.cc +++ b/tests/uncompressed_encode.cc @@ -565,7 +565,7 @@ struct heif_image *createImage_RGBA_planar() return image; } -static void do_encode(heif_image* input_image, const char* filename, bool check_decode) +static void do_encode(heif_image* input_image, const char* filename, bool check_decode, uint8_t prefer_uncC_short_form = 0) { REQUIRE(input_image != nullptr); @@ -580,6 +580,7 @@ static void do_encode(heif_image* input_image, const char* filename, bool check_ options->macOS_compatibility_workaround = false; options->macOS_compatibility_workaround_no_nclx_profile = true; options->image_orientation = heif_orientation_normal; + options->prefer_uncC_short_form = prefer_uncC_short_form; heif_image_handle *output_image_handle; err = heif_context_encode_image(ctx, input_image, encoder, options, &output_image_handle); @@ -644,6 +645,11 @@ TEST_CASE("Encode Mono") do_encode(input_image, "encode_mono.heif", true); } +TEST_CASE("Encode RGB Version1") +{ + heif_image *input_image = createImage_RGB_interleaved(); + do_encode(input_image, "encode_rgb_version1.heif", true, true); +} TEST_CASE("Encode Mono with alpha") { @@ -708,6 +714,12 @@ TEST_CASE("Encode RGBA") do_encode(input_image, "encode_rgba.heif", true); } +TEST_CASE("Encode RGBA Version 1") +{ + heif_image *input_image = createImage_RGBA_interleaved(); + do_encode(input_image, "encode_rgba_version1.heif", true, true); +} + TEST_CASE("Encode RRRGGBBAA_LE 10 bit") { diff --git a/third-party/dav1d.cmd b/third-party/dav1d.cmd index e546222bd9..0c238d8a5b 100644 --- a/third-party/dav1d.cmd +++ b/third-party/dav1d.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 1.4.1 --depth 1 https://code.videolan.org/videolan/dav1d.git +git clone -b 1.4.3 --depth 1 https://code.videolan.org/videolan/dav1d.git cd dav1d 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 3160622952..b45d56a11b 100644 --- a/third-party/svt.cmd +++ b/third-party/svt.cmd @@ -5,12 +5,12 @@ : # 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.0 --depth 1 https://gitlab.com/AOMediaCodec/SVT-AV1.git +git clone -b v2.1.1 --depth 1 https://gitlab.com/AOMediaCodec/SVT-AV1.git cd SVT-AV1 cd Build/linux -./build.sh release static no-dec no-apps prefix=$(pwd)/install install +./build.sh release static no-apps prefix=$(pwd)/install install cd ../../.. echo ""