diff --git a/.github/workflows/analysis_workflow.yml b/.github/workflows/analysis_workflow.yml index f465b51098..d722232129 100644 --- a/.github/workflows/analysis_workflow.yml +++ b/.github/workflows/analysis_workflow.yml @@ -59,7 +59,7 @@ jobs: cmake ../. \ -DCMAKE_INSTALL_PREFIX=../_install \ -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_CXX_STANDARD=14 \ + -DCMAKE_CXX_STANDARD=17 \ -DCMAKE_CXX_FLAGS="-g -O0 -fprofile-arcs -ftest-coverage" \ -DCMAKE_CXX_OUTPUT_EXTENSION_REPLACE=ON \ -DCMAKE_EXE_LINKER_FLAGS="-lgcov" \ diff --git a/.github/workflows/ci_workflow.yml b/.github/workflows/ci_workflow.yml index ffb0c4c10e..7b3e1d9338 100644 --- a/.github/workflows/ci_workflow.yml +++ b/.github/workflows/ci_workflow.yml @@ -57,13 +57,55 @@ jobs: container: # DockerHub: https://hub.docker.com/u/aswf # Source: https://github.com/AcademySoftwareFoundation/aswf-docker - image: aswf/ci-ocio:${{ matrix.vfx-cy }}.1 + image: aswf/ci-ocio:${{ matrix.vfx-cy }} strategy: - fail-fast: true + fail-fast: false matrix: - build: [7, 8, 9, 10, 11, 12] + build: [7, 8, 9, 10, 11, 12, 13, 14, 15] include: # ------------------------------------------------------------------- + # VFX CY2025 (Python 3.11) + # ------------------------------------------------------------------- + - build: 15 + build-type: Debug + build-shared: 'ON' + build-docs: 'OFF' + build-openfx: 'ON' + use-simd: 'ON' + use-oiio: 'ON' + cxx-standard: 20 + cxx-compiler: clang++ + cc-compiler: clang + compiler-desc: Clang + vfx-cy: 2025 + install-ext-packages: MISSING + - build: 14 + build-type: Release + build-shared: 'ON' + build-docs: 'ON' + build-openfx: 'ON' + use-simd: 'OFF' + use-oiio: 'OFF' + cxx-standard: 17 + cxx-compiler: g++ + cc-compiler: gcc + compiler-desc: GCC + vfx-cy: 2025 + install-ext-packages: ALL + - build: 13 + build-type: Release + build-shared: 'OFF' + build-docs: 'OFF' + build-openfx: 'OFF' + use-simd: 'ON' + use-oiio: 'OFF' + cxx-standard: 17 + cxx-compiler: g++ + cc-compiler: gcc + compiler-desc: GCC + vfx-cy: 2025 + install-ext-packages: ALL + # ------------------------------------------------------------------- # VFX CY2024 (Python 3.11) # ------------------------------------------------------------------- - build: 12 @@ -73,7 +115,7 @@ jobs: build-openfx: 'ON' use-simd: 'ON' use-oiio: 'ON' - cxx-standard: 17 + cxx-standard: 20 cxx-compiler: clang++ cc-compiler: clang compiler-desc: Clang @@ -99,7 +141,7 @@ jobs: build-openfx: 'OFF' use-simd: 'ON' use-oiio: 'OFF' - cxx-standard: 11 + cxx-standard: 17 cxx-compiler: g++ cc-compiler: gcc compiler-desc: GCC @@ -115,7 +157,7 @@ jobs: build-openfx: 'ON' use-simd: 'ON' use-oiio: 'ON' - cxx-standard: 17 + cxx-standard: 20 cxx-compiler: clang++ cc-compiler: clang compiler-desc: Clang @@ -141,7 +183,7 @@ jobs: build-openfx: 'OFF' use-simd: 'ON' use-oiio: 'OFF' - cxx-standard: 11 + cxx-standard: 17 cxx-compiler: g++ cc-compiler: gcc compiler-desc: GCC @@ -165,6 +207,7 @@ jobs: - name: Configure run: | cmake ../. \ + -DOCIO_VERBOSE=ON \ -DCMAKE_INSTALL_PREFIX=../_install \ -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \ -DCMAKE_CXX_STANDARD=${{ matrix.cxx-standard }} \ @@ -209,6 +252,7 @@ jobs: # the static version of the package. run: | cmake . \ + -DOCIO_VERBOSE=ON \ -DCMAKE_PREFIX_PATH=../../../_install \ -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \ -Dexpat_ROOT=${{ env.ocio_build_path }}/ext/dist \ @@ -218,7 +262,7 @@ jobs: -Dpystring_ROOT=${{ env.ocio_build_path }}/ext/dist \ -Dyaml-cpp_ROOT=${{ env.ocio_build_path }}/ext/dist \ -Dyaml-cpp_STATIC_LIBRARY=ON \ - -Dyaml-cpp_VERSION=0.7.0 \ + -Dyaml-cpp_VERSION=0.8.0 \ -DZLIB_ROOT=${{ env.ocio_build_path }}/ext/dist \ -DZLIB_STATIC_LIBRARY=ON \ -Dminizip-ng_ROOT=${{ env.ocio_build_path }}/ext/dist \ @@ -255,14 +299,14 @@ jobs: - /node20217:/node20217:rw,rshared - /node20217:/__e/node20:ro,rshared strategy: - fail-fast: true + fail-fast: false matrix: - build: [1, 2, 3, 4, 5, 6] + build: [1, 2, 3] include: # ------------------------------------------------------------------- # VFX CY2022 (Python 3.9) # ------------------------------------------------------------------- - - build: 6 + - build: 1 build-type: Debug build-shared: 'ON' build-docs: 'OFF' @@ -275,7 +319,7 @@ jobs: compiler-desc: Clang vfx-cy: 2022 install-ext-packages: ALL - - build: 5 + - build: 2 build-type: Release build-shared: 'ON' build-docs: 'ON' @@ -288,60 +332,18 @@ jobs: compiler-desc: GCC vfx-cy: 2022 install-ext-packages: MISSING - - build: 4 - build-type: Release - build-shared: 'OFF' - build-docs: 'OFF' - build-openfx: 'OFF' - use-simd: 'ON' - use-oiio: 'OFF' - cxx-standard: 11 - cxx-compiler: g++ - cc-compiler: gcc - compiler-desc: GCC - vfx-cy: 2022 - install-ext-packages: ALL - # ------------------------------------------------------------------- - # VFX CY2021 (Python 3.7) - # ------------------------------------------------------------------- - build: 3 - build-type: Release - build-shared: 'ON' - build-docs: 'OFF' - build-openfx: 'OFF' - use-simd: 'ON' - use-oiio: 'ON' - cxx-standard: 17 - cxx-compiler: clang++ - cc-compiler: clang - compiler-desc: Clang - vfx-cy: 2021 - install-ext-packages: MISSING - - build: 2 build-type: Release build-shared: 'OFF' build-docs: 'OFF' - build-openfx: 'ON' - use-simd: 'OFF' - use-oiio: 'OFF' - cxx-standard: 14 - cxx-compiler: clang++ - cc-compiler: clang - compiler-desc: Clang - vfx-cy: 2021 - install-ext-packages: ALL - - build: 1 - build-type: Debug - build-shared: 'ON' - build-docs: 'OFF' build-openfx: 'OFF' use-simd: 'ON' use-oiio: 'OFF' - cxx-standard: 11 + cxx-standard: 17 cxx-compiler: g++ cc-compiler: gcc compiler-desc: GCC - vfx-cy: 2021 + vfx-cy: 2022 install-ext-packages: ALL env: CXX: ${{ matrix.cxx-compiler }} @@ -377,6 +379,7 @@ jobs: - name: Configure run: | cmake ../. \ + -DOCIO_VERBOSE=ON \ -DCMAKE_INSTALL_PREFIX=../_install \ -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \ -DCMAKE_CXX_STANDARD=${{ matrix.cxx-standard }} \ @@ -421,6 +424,7 @@ jobs: # the static version of the package. run: | cmake . \ + -DOCIO_VERBOSE=ON \ -DCMAKE_PREFIX_PATH=../../../_install \ -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \ -Dexpat_ROOT=${{ env.ocio_build_path }}/ext/dist \ @@ -430,7 +434,7 @@ jobs: -Dpystring_ROOT=${{ env.ocio_build_path }}/ext/dist \ -Dyaml-cpp_ROOT=${{ env.ocio_build_path }}/ext/dist \ -Dyaml-cpp_STATIC_LIBRARY=ON \ - -Dyaml-cpp_VERSION=0.7.0 \ + -Dyaml-cpp_VERSION=0.8.0 \ -DZLIB_ROOT=${{ env.ocio_build_path }}/ext/dist \ -DZLIB_STATIC_LIBRARY=ON \ -Dminizip-ng_ROOT=${{ env.ocio_build_path }}/ext/dist \ @@ -461,6 +465,7 @@ jobs: github.event.pull_request.head.repo.full_name != github.repository runs-on: macos-13 strategy: + fail-fast: false matrix: build: [1, 2, 3, 4, 5] include: @@ -472,8 +477,8 @@ jobs: build-openfx: 'ON' use-simd: 'ON' use-oiio: 'ON' - cxx-standard: 17 - python-version: '3.11' + cxx-standard: 20 + python-version: '3.13' # Keeping one universal build - build: 4 arch-type: "x86_64;arm64" @@ -483,8 +488,8 @@ jobs: build-openfx: 'OFF' use-simd: 'ON' use-oiio: 'OFF' - cxx-standard: 11 - python-version: '3.10' + cxx-standard: 20 + python-version: '3.12' - build: 3 arch-type: "x86_64" build-type: Release @@ -493,8 +498,8 @@ jobs: build-openfx: 'OFF' use-simd: 'OFF' use-oiio: 'OFF' - cxx-standard: 11 - python-version: '3.10' + cxx-standard: 17 + python-version: '3.11' - build: 2 arch-type: "x86_64" build-type: Debug @@ -503,8 +508,8 @@ jobs: build-openfx: 'ON' use-simd: 'ON' use-oiio: 'OFF' - cxx-standard: 11 - python-version: '3.9' + cxx-standard: 17 + python-version: '3.10' - build: 1 arch-type: "x86_64" build-type: Release @@ -513,8 +518,8 @@ jobs: build-openfx: 'ON' use-simd: 'ON' use-oiio: 'OFF' - cxx-standard: 14 - python-version: '3.7' + cxx-standard: 17 + python-version: '3.9' steps: - name: Setup Python uses: actions/setup-python@v5 @@ -534,6 +539,7 @@ jobs: - name: Configure run: | cmake ../. \ + -DOCIO_VERBOSE=ON \ -DCMAKE_INSTALL_PREFIX=../_install \ -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \ -DCMAKE_CXX_STANDARD=${{ matrix.cxx-standard }} \ @@ -579,6 +585,7 @@ jobs: # the static version of the package. run: | cmake . \ + -DOCIO_VERBOSE=ON \ -DCMAKE_PREFIX_PATH=../../../_install \ -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \ -Dexpat_ROOT=${{ env.ocio_build_path }}/ext/dist \ @@ -588,7 +595,7 @@ jobs: -Dpystring_ROOT=${{ env.ocio_build_path }}/ext/dist \ -Dyaml-cpp_ROOT=${{ env.ocio_build_path }}/ext/dist \ -Dyaml-cpp_STATIC_LIBRARY=ON \ - -Dyaml-cpp_VERSION=0.7.0 \ + -Dyaml-cpp_VERSION=0.8.0 \ -DZLIB_ROOT=${{ env.ocio_build_path }}/ext/dist \ -DZLIB_STATIC_LIBRARY=ON \ -Dminizip-ng_ROOT=${{ env.ocio_build_path }}/ext/dist \ @@ -616,6 +623,7 @@ jobs: runs-on: macos-14 strategy: + fail-fast: false matrix: build: [1, 2] include: @@ -628,8 +636,8 @@ jobs: build-openfx: 'OFF' use-simd: 'ON' use-oiio: 'OFF' - cxx-standard: 11 - python-version: '3.11' + cxx-standard: 20 + python-version: '3.13' - build: 2 arch-type: "x86_64;arm64" test-rosetta: "ON" @@ -639,8 +647,8 @@ jobs: build-openfx: 'OFF' use-simd: 'ON' use-oiio: 'OFF' - cxx-standard: 11 - python-version: '3.11' + cxx-standard: 17 + python-version: '3.12' steps: - name: Setup Python uses: actions/setup-python@v5 @@ -660,6 +668,7 @@ jobs: - name: Configure run: | cmake ../. \ + -DOCIO_VERBOSE=ON \ -DCMAKE_INSTALL_PREFIX=../_install \ -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \ -DCMAKE_CXX_STANDARD=${{ matrix.cxx-standard }} \ @@ -710,6 +719,7 @@ jobs: # the static version of the package. run: | cmake . \ + -DOCIO_VERBOSE=ON \ -DCMAKE_PREFIX_PATH=../../../_install \ -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \ -Dexpat_ROOT=${{ env.ocio_build_path }}/ext/dist \ @@ -719,7 +729,7 @@ jobs: -Dpystring_ROOT=${{ env.ocio_build_path }}/ext/dist \ -Dyaml-cpp_ROOT=${{ env.ocio_build_path }}/ext/dist \ -Dyaml-cpp_STATIC_LIBRARY=ON \ - -Dyaml-cpp_VERSION=0.7.0 \ + -Dyaml-cpp_VERSION=0.8.0 \ -DZLIB_ROOT=${{ env.ocio_build_path }}/ext/dist \ -DZLIB_STATIC_LIBRARY=ON \ -Dminizip-ng_ROOT=${{ env.ocio_build_path }}/ext/dist \ @@ -751,6 +761,7 @@ jobs: github.event.pull_request.head.repo.full_name != github.repository runs-on: windows-2022 strategy: + fail-fast: false matrix: build: [1, 2, 3, 4] include: @@ -761,8 +772,8 @@ jobs: build-openfx: 'ON' use-simd: 'OFF' use-oiio: 'ON' - cxx-standard: 17 - python-version: '3.11' + cxx-standard: 20 + python-version: '3.13' - build: 3 build-type: Release build-shared: 'OFF' @@ -770,8 +781,8 @@ jobs: build-openfx: 'OFF' use-simd: 'ON' use-oiio: 'OFF' - cxx-standard: 11 - python-version: '3.9' + cxx-standard: 17 + python-version: '3.12' - build: 2 build-type: Debug build-shared: 'ON' @@ -779,8 +790,8 @@ jobs: build-openfx: 'OFF' use-simd: 'ON' use-oiio: 'OFF' - cxx-standard: 11 - python-version: '3.8' + cxx-standard: 17 + python-version: '3.11' # C++14, OpenEXR, OpenFX - build: 1 build-type: Release @@ -789,8 +800,8 @@ jobs: build-openfx: 'ON' use-simd: 'ON' use-oiio: 'OFF' - cxx-standard: 14 - python-version: '3.7' + cxx-standard: 17 + python-version: '3.9' steps: - name: Setup Python uses: actions/setup-python@v5 @@ -816,6 +827,7 @@ jobs: - name: Configure run: | cmake ../. \ + -DOCIO_VERBOSE=ON \ -DCMAKE_INSTALL_PREFIX=../_install \ -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \ -DCMAKE_CXX_STANDARD=${{ matrix.cxx-standard }} \ @@ -865,6 +877,7 @@ jobs: # the static version of the package. run: | cmake . \ + -DOCIO_VERBOSE=ON \ -DCMAKE_PREFIX_PATH=../../../_install \ -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \ -Dexpat_ROOT=${{ env.ocio_build_path }}/ext/dist \ @@ -874,7 +887,7 @@ jobs: -Dpystring_ROOT=${{ env.ocio_build_path }}/ext/dist \ -Dyaml-cpp_ROOT=${{ env.ocio_build_path }}/ext/dist \ -Dyaml-cpp_STATIC_LIBRARY=ON \ - -Dyaml-cpp_VERSION=0.7.0 \ + -Dyaml-cpp_VERSION=0.8.0 \ -DZLIB_ROOT=${{ env.ocio_build_path }}/ext/dist \ -DZLIB_STATIC_LIBRARY=ON \ -Dminizip-ng_ROOT=${{ env.ocio_build_path }}/ext/dist \ diff --git a/.github/workflows/dependencies_latest.yml b/.github/workflows/dependencies_latest.yml index 9cebbb3ce5..ced7859f64 100644 --- a/.github/workflows/dependencies_latest.yml +++ b/.github/workflows/dependencies_latest.yml @@ -27,18 +27,18 @@ jobs: # --------------------------------------------------------------------------- linux_latest: - name: 'Linux CentOS 7 VFX CY${{ matrix.vfx-cy }} latest + name: 'Linux VFX CY${{ matrix.vfx-cy }} latest <${{ matrix.compiler-desc }} cxx=${{ matrix.cxx-standard }}, docs=${{ matrix.build-docs }}>' # Don't run on OCIO forks - if: github.repository == 'AcademySoftwareFoundation/OpenColorIO' - # GH-hosted VM. The build runs in CentOS 7 'container' defined below. + # if: github.repository == 'AcademySoftwareFoundation/OpenColorIO' + # GH-hosted VM. The build runs in ASWF 'container' defined below. runs-on: ubuntu-latest container: # DockerHub: https://hub.docker.com/u/aswf # Source: https://github.com/AcademySoftwareFoundation/aswf-docker - image: aswf/ci-base:${{ matrix.vfx-cy }} + image: aswf/ci-base:${{ matrix.vfx-cy }}.1 strategy: matrix: build: [1, 2, 3, 4] @@ -49,42 +49,38 @@ jobs: - build: 1 build-docs: 'ON' build-openfx: 'ON' - cxx-standard: 17 + cxx-standard: 20 cxx-compiler: g++ cc-compiler: gcc compiler-desc: GCC - vfx-cy: 2022 - use-oiio: 'ON' + vfx-cy: 2025 - build: 2 build-docs: 'OFF' build-openfx: 'OFF' - cxx-standard: 14 + cxx-standard: 17 cxx-compiler: g++ cc-compiler: gcc compiler-desc: GCC - vfx-cy: 2021 - use-oiio: 'OFF' + vfx-cy: 2024 # ------------------------------------------------------------------- # Clang # ------------------------------------------------------------------- - build: 3 build-docs: 'OFF' build-openfx: 'OFF' - cxx-standard: 17 + cxx-standard: 20 cxx-compiler: clang++ cc-compiler: clang compiler-desc: Clang - vfx-cy: 2022 - use-oiio: 'OFF' + vfx-cy: 2025 - build: 4 build-docs: 'ON' build-openfx: 'ON' - cxx-standard: 14 + cxx-standard: 17 cxx-compiler: clang++ cc-compiler: clang compiler-desc: Clang - vfx-cy: 2021 - use-oiio: 'ON' + vfx-cy: 2024 env: CXX: ${{ matrix.cxx-compiler }} CC: ${{ matrix.cc-compiler }} @@ -100,25 +96,20 @@ jobs: run: | EXT_PATH=/usr/local echo "EXT_PATH=$EXT_PATH" >> $GITHUB_ENV - - name: Install indirect dependencies - run: | - share/ci/scripts/multi/install_pugixml.sh latest - name: Install fixed ext package versions # Minizip-ng depends on ZLIB. ZLIB must be installed first. run: | - share/ci/scripts/multi/install_expat.sh 2.4.1 $EXT_PATH - share/ci/scripts/multi/install_lcms2.sh 2.2 $EXT_PATH - share/ci/scripts/multi/install_yaml-cpp.sh 0.7.0 $EXT_PATH - share/ci/scripts/multi/install_pystring.sh 1.1.3 $EXT_PATH - share/ci/scripts/multi/install_pybind11.sh 2.9.2 $EXT_PATH - share/ci/scripts/multi/install_zlib.sh 1.2.12 $EXT_PATH - share/ci/scripts/multi/install_minizip-ng.sh 3.0.6 $EXT_PATH + share/ci/scripts/multi/install_expat.sh 2.7.2 $EXT_PATH + share/ci/scripts/multi/install_lcms2.sh 2.17 $EXT_PATH + share/ci/scripts/multi/install_yaml-cpp.sh 0.8.0 $EXT_PATH + share/ci/scripts/multi/install_pystring.sh 1.1.4 $EXT_PATH + share/ci/scripts/multi/install_pybind11.sh 3.0.1 $EXT_PATH + share/ci/scripts/multi/install_zlib.sh 1.3.1 $EXT_PATH + share/ci/scripts/multi/install_minizip-ng.sh 4.0.10 $EXT_PATH - name: Install latest ext package versions run: | share/ci/scripts/multi/install_imath.sh latest $EXT_PATH share/ci/scripts/multi/install_openexr.sh latest $EXT_PATH - share/ci/scripts/multi/install_oiio.sh latest $EXT_PATH - share/ci/scripts/multi/install_osl.sh latest $EXT_PATH share/ci/scripts/multi/install_openfx.sh latest $EXT_PATH - name: Create build directories run: | @@ -135,8 +126,7 @@ jobs: -DOCIO_BUILD_GPU_TESTS=OFF \ -DOCIO_INSTALL_EXT_PACKAGES=NONE \ -DOCIO_WARNING_AS_ERROR=OFF \ - -DPython_EXECUTABLE=$(which python) \ - -DOCIO_USE_OIIO_FOR_APPS=${{ matrix.use-oiio }} + -DPython_EXECUTABLE=$(which python) working-directory: _build - name: Build run: | @@ -169,7 +159,7 @@ jobs: docs=${{ matrix.build-docs }}, python=${{ matrix.python-version }}>' # Don't run on OCIO forks - if: github.repository == 'AcademySoftwareFoundation/OpenColorIO' + # if: github.repository == 'AcademySoftwareFoundation/OpenColorIO' runs-on: macos-latest strategy: matrix: @@ -178,15 +168,13 @@ jobs: - build: 1 build-docs: 'ON' build-openfx: 'ON' - cxx-standard: 17 - python-version: '3.11' - use-oiio: 'ON' + cxx-standard: 20 + python-version: '3.13' - build: 2 build-docs: 'ON' build-openfx: 'ON' - cxx-standard: 14 + cxx-standard: 17 python-version: '3.9' - use-oiio: 'OFF' steps: - name: Setup Python uses: actions/setup-python@v5 @@ -201,29 +189,23 @@ jobs: run: share/ci/scripts/macos/install_tests_env.sh - name: Setup ext environment run: | - EXT_PATH=/usr/local + EXT_PATH=$GITHUB_WORKSPACE/_ext echo "EXT_PATH=$EXT_PATH" >> $GITHUB_ENV - - name: Install indirect dependencies - run: | - share/ci/scripts/macos/install_bison.sh latest - share/ci/scripts/macos/install_boost.sh latest - share/ci/scripts/multi/install_pugixml.sh latest $EXT_PATH + echo "CMAKE_PREFIX_PATH=$EXT_PATH" >> $GITHUB_ENV - name: Install fixed ext package versions # Minizip-ng depends on ZLIB. ZLIB must be installed first. run: | - share/ci/scripts/multi/install_expat.sh 2.4.1 $EXT_PATH - share/ci/scripts/multi/install_lcms2.sh 2.2 $EXT_PATH - share/ci/scripts/multi/install_yaml-cpp.sh 0.7.0 $EXT_PATH - share/ci/scripts/multi/install_pystring.sh 1.1.3 $EXT_PATH - share/ci/scripts/multi/install_pybind11.sh 2.9.2 $EXT_PATH - share/ci/scripts/multi/install_zlib.sh 1.2.12 $EXT_PATH - share/ci/scripts/multi/install_minizip-ng.sh 3.0.6 $EXT_PATH + share/ci/scripts/multi/install_expat.sh 2.7.2 $EXT_PATH + share/ci/scripts/multi/install_lcms2.sh 2.17 $EXT_PATH + share/ci/scripts/multi/install_yaml-cpp.sh 0.8.0 $EXT_PATH + share/ci/scripts/multi/install_pystring.sh 1.1.4 $EXT_PATH + share/ci/scripts/multi/install_pybind11.sh 3.0.1 $EXT_PATH + share/ci/scripts/multi/install_zlib.sh 1.3.1 $EXT_PATH + share/ci/scripts/multi/install_minizip-ng.sh 4.0.10 $EXT_PATH - name: Install latest ext package versions run: | share/ci/scripts/multi/install_imath.sh latest $EXT_PATH share/ci/scripts/multi/install_openexr.sh latest $EXT_PATH - share/ci/scripts/multi/install_oiio.sh latest $EXT_PATH - share/ci/scripts/multi/install_osl.sh latest $EXT_PATH share/ci/scripts/multi/install_openfx.sh latest $EXT_PATH - name: Create build directories run: | @@ -240,8 +222,7 @@ jobs: -DOCIO_BUILD_GPU_TESTS=OFF \ -DOCIO_INSTALL_EXT_PACKAGES=NONE \ -DOCIO_WARNING_AS_ERROR=OFF \ - -DPython_EXECUTABLE=$(which python) \ - -DOCIO_USE_OIIO_FOR_APPS=${{ matrix.use-oiio }} + -DPython_EXECUTABLE=$(which python) working-directory: _build - name: Build run: | @@ -260,6 +241,7 @@ jobs: -DCMAKE_BUILD_TYPE=Release cmake --build . \ --config Release + export DYLD_LIBRARY_PATH=$EXT_PATH/lib:$DYLD_LIBRARY_PATH ./consumer working-directory: _build/tests/cmake-consumer-dist @@ -274,7 +256,7 @@ jobs: docs=${{ matrix.build-docs }}, python=${{ matrix.python-version }}>' # Don't run on OCIO forks - if: github.repository == 'AcademySoftwareFoundation/OpenColorIO' + # if: github.repository == 'AcademySoftwareFoundation/OpenColorIO' runs-on: windows-latest strategy: matrix: @@ -283,13 +265,13 @@ jobs: - build: 1 build-docs: 'ON' build-openfx: 'ON' - cxx-standard: 17 - python-version: '3.11' + cxx-standard: 20 + python-version: '3.13' use-oiio: 'ON' - build: 2 build-docs: 'ON' build-openfx: 'ON' - cxx-standard: 14 + cxx-standard: 17 python-version: '3.9' use-oiio: 'OFF' steps: @@ -323,32 +305,23 @@ jobs: run: | vcpkg install zlib:x64-windows vcpkg install tiff:x64-windows - vcpkg install boost-asio:x64-windows - vcpkg install boost-container:x64-windows - vcpkg install boost-filesystem:x64-windows - vcpkg install boost-math:x64-windows - vcpkg install boost-stacktrace:x64-windows - vcpkg install boost-system:x64-windows - vcpkg install boost-thread:x64-windows - share/ci/scripts/multi/install_pugixml.sh latest $EXT_PATH shell: bash - name: Install fixed ext package versions # Minizip-ng depends on ZLIB. ZLIB must be installed first. run: | - share/ci/scripts/multi/install_lcms2.sh 2.2 $EXT_PATH - share/ci/scripts/multi/install_yaml-cpp.sh 0.7.0 $EXT_PATH - share/ci/scripts/multi/install_pystring.sh 1.1.3 $EXT_PATH - share/ci/scripts/multi/install_pybind11.sh 2.9.2 $EXT_PATH - share/ci/scripts/multi/install_expat.sh 2.4.1 $EXT_PATH - share/ci/scripts/multi/install_zlib.sh 1.2.12 $EXT_PATH - share/ci/scripts/multi/install_minizip-ng.sh 3.0.6 $EXT_PATH + share/ci/scripts/multi/install_lcms2.sh 2.17 $EXT_PATH + share/ci/scripts/multi/install_yaml-cpp.sh 0.8.0 $EXT_PATH + share/ci/scripts/multi/install_pystring.sh 1.1.4 $EXT_PATH + share/ci/scripts/multi/install_pybind11.sh 3.0.1 $EXT_PATH + share/ci/scripts/multi/install_expat.sh 2.7.2 $EXT_PATH + share/ci/scripts/multi/install_zlib.sh 1.3.1 $EXT_PATH + share/ci/scripts/multi/install_minizip-ng.sh 4.0.10 $EXT_PATH shell: bash # OSL not installed due to LLVM compilation time. - name: Install latest ext package versions run: | share/ci/scripts/multi/install_imath.sh latest $EXT_PATH share/ci/scripts/multi/install_openexr.sh latest $EXT_PATH - share/ci/scripts/multi/install_oiio.sh latest $EXT_PATH share/ci/scripts/multi/install_openfx.sh latest $EXT_PATH shell: bash - name: Create build directories @@ -369,8 +342,7 @@ jobs: -DOCIO_INSTALL_EXT_PACKAGES=NONE \ -DOCIO_WARNING_AS_ERROR=OFF \ -DPython_EXECUTABLE=$(which python) \ - -DOCIO_BUILD_PYTHON=OFF \ - -DOCIO_USE_OIIO_FOR_APPS=${{ matrix.use-oiio }} + -DOCIO_BUILD_PYTHON=OFF shell: bash working-directory: _build - name: Build diff --git a/.github/workflows/platform_latest.yml b/.github/workflows/platform_latest.yml index ab1749bab5..c6f1ab1c26 100644 --- a/.github/workflows/platform_latest.yml +++ b/.github/workflows/platform_latest.yml @@ -33,7 +33,7 @@ jobs: shared=${{ matrix.build-shared }}, cxx=${{ matrix.cxx-standard }}>' # Don't run on OCIO forks - if: github.repository == 'AcademySoftwareFoundation/OpenColorIO' + # if: github.repository == 'AcademySoftwareFoundation/OpenColorIO' runs-on: ubuntu-latest strategy: matrix: @@ -153,7 +153,7 @@ jobs: -Dpystring_ROOT=${{ env.ocio_build_path }}/ext/dist \ -Dyaml-cpp_ROOT=${{ env.ocio_build_path }}/ext/dist \ -Dyaml-cpp_STATIC_LIBRARY=ON \ - -Dyaml-cpp_VERSION=0.7.0 \ + -Dyaml-cpp_VERSION=0.8.0 \ -DZLIB_ROOT=${{ env.ocio_build_path }}/ext/dist \ -DZLIB_STATIC_LIBRARY=ON \ -Dminizip-ng_ROOT=${{ env.ocio_build_path }}/ext/dist \ @@ -175,7 +175,7 @@ jobs: cxx=${{ matrix.cxx-standard }}, python=${{ matrix.python-version }}>' # Don't run on OCIO forks - if: github.repository == 'AcademySoftwareFoundation/OpenColorIO' + # if: github.repository == 'AcademySoftwareFoundation/OpenColorIO' runs-on: macos-latest strategy: matrix: @@ -187,14 +187,14 @@ jobs: build-shared: ON cxx-standard: 23 enable-sanitizer: OFF - python-version: '3.11' + python-version: '3.13' - build: 2 build-python: OFF build-type: Debug build-shared: ON cxx-standard: 23 enable-sanitizer: ON - python-version: '3.11' + python-version: '3.9' steps: - name: Setup Python uses: actions/setup-python@v5 @@ -266,7 +266,7 @@ jobs: -Dpystring_ROOT=${{ env.ocio_build_path }}/ext/dist \ -Dyaml-cpp_ROOT=${{ env.ocio_build_path }}/ext/dist \ -Dyaml-cpp_STATIC_LIBRARY=ON \ - -Dyaml-cpp_VERSION=0.7.0 \ + -Dyaml-cpp_VERSION=0.8.0 \ -DZLIB_ROOT=${{ env.ocio_build_path }}/ext/dist \ -DZLIB_STATIC_LIBRARY=ON \ -Dminizip-ng_ROOT=${{ env.ocio_build_path }}/ext/dist \ @@ -288,7 +288,7 @@ jobs: cxx=${{ matrix.cxx-standard }}, python=${{ matrix.python-version }}>' # Don't run on OCIO forks - if: github.repository == 'AcademySoftwareFoundation/OpenColorIO' + # if: github.repository == 'AcademySoftwareFoundation/OpenColorIO' runs-on: windows-latest strategy: matrix: @@ -298,14 +298,14 @@ jobs: build-python: ON build-type: Release build-shared: ON - cxx-standard: 23 - python-version: '3.11' + cxx-standard: 20 + python-version: '3.13' - build: 2 build-python: ON build-type: Debug build-shared: ON - cxx-standard: 23 - python-version: '3.11' + cxx-standard: 20 + python-version: '3.9' steps: - name: Setup Python uses: actions/setup-python@v5 @@ -387,7 +387,7 @@ jobs: -Dpystring_ROOT=${{ env.ocio_build_path }}/ext/dist \ -Dyaml-cpp_ROOT=${{ env.ocio_build_path }}/ext/dist \ -Dyaml-cpp_STATIC_LIBRARY=ON \ - -Dyaml-cpp_VERSION=0.7.0 \ + -Dyaml-cpp_VERSION=0.8.0 \ -DZLIB_ROOT=${{ env.ocio_build_path }}/ext/dist \ -DZLIB_STATIC_LIBRARY=ON \ -Dminizip-ng_ROOT=${{ env.ocio_build_path }}/ext/dist \ diff --git a/.github/workflows/wheel_workflow.yml b/.github/workflows/wheel_workflow.yml index 376b006c04..f0877d8637 100644 --- a/.github/workflows/wheel_workflow.yml +++ b/.github/workflows/wheel_workflow.yml @@ -81,10 +81,6 @@ jobs: # ------------------------------------------------------------------- # CPython 64 bits manylinux_2_28 # ------------------------------------------------------------------- - - build: CPython 3.8 64 bits manylinux_2_28 - manylinux: manylinux_2_28 - python: cp38-manylinux_x86_64 - arch: x86_64 - build: CPython 3.9 64 bits manylinux_2_28 manylinux: manylinux_2_28 python: cp39-manylinux_x86_64 @@ -105,13 +101,13 @@ jobs: manylinux: manylinux_2_28 python: cp313-manylinux_x86_64 arch: x86_64 + - build: CPython 3.14 64 bits manylinux_2_28 + manylinux: manylinux_2_28 + python: cp314-manylinux_x86_64 + arch: x86_64 # ------------------------------------------------------------------- # CPython 64 bits manylinux2014 # ------------------------------------------------------------------- - - build: CPython 3.8 64 bits manylinux2014 - manylinux: manylinux2014 - python: cp38-manylinux_x86_64 - arch: x86_64 - build: CPython 3.9 64 bits manylinux2014 manylinux: manylinux2014 python: cp39-manylinux_x86_64 @@ -132,6 +128,10 @@ jobs: manylinux: manylinux2014 python: cp313-manylinux_x86_64 arch: x86_64 + - build: CPython 3.14 64 bits manylinux2014 + manylinux: manylinux2014 + python: cp314-manylinux_x86_64 + arch: x86_64 steps: - uses: actions/checkout@v4 @@ -139,10 +139,10 @@ jobs: - uses: actions/setup-python@v5 name: Install Python with: - python-version: '3.9' + python-version: '3.11' - name: Build wheels - uses: pypa/cibuildwheel@v2.22.0 + uses: pypa/cibuildwheel@v3.1.4 env: CIBW_BUILD: ${{ matrix.python }} CIBW_ARCHS: ${{ matrix.arch }} @@ -170,10 +170,6 @@ jobs: # ------------------------------------------------------------------- # CPython ARM 64 bits manylinux2014 # ------------------------------------------------------------------- - - build: CPython 3.8 ARM 64 bits manylinux2014 - manylinux: manylinux2014 - python: cp38-manylinux_aarch64 - arch: aarch64 - build: CPython 3.9 ARM 64 bits manylinux2014 manylinux: manylinux2014 python: cp39-manylinux_aarch64 @@ -194,6 +190,10 @@ jobs: manylinux: manylinux2014 python: cp313-manylinux_aarch64 arch: aarch64 + - build: CPython 3.14 ARM 64 bits manylinux2014 + manylinux: manylinux2014 + python: cp314-manylinux_aarch64 + arch: aarch64 steps: - uses: actions/checkout@v4 @@ -201,10 +201,10 @@ jobs: - uses: actions/setup-python@v5 name: Install Python with: - python-version: '3.9' + python-version: '3.11' - name: Build wheels - uses: pypa/cibuildwheel@v2.22.0 + uses: pypa/cibuildwheel@v3.1.4 env: CIBW_BUILD: ${{ matrix.python }} CIBW_ARCHS: ${{ matrix.arch }} @@ -232,9 +232,6 @@ jobs: # ------------------------------------------------------------------- # CPython 64 bits # ------------------------------------------------------------------- - - build: CPython 3.8 64 bits - python: cp38-macosx_x86_64 - arch: x86_64 - build: CPython 3.9 64 bits python: cp39-macosx_x86_64 arch: x86_64 @@ -250,6 +247,9 @@ jobs: - build: CPython 3.13 64 bits python: cp313-macosx_x86_64 arch: x86_64 + - build: CPython 3.14 64 bits + python: cp314-macosx_x86_64 + arch: x86_64 steps: - uses: actions/checkout@v4 @@ -257,10 +257,14 @@ jobs: - uses: actions/setup-python@v5 name: Install Python with: - python-version: '3.9' + python-version: '3.11' + - name: Remove brew OpenEXR/Imath + run: | + brew uninstall --ignore-dependencies openexr imath || true + - name: Build wheels - uses: pypa/cibuildwheel@v2.22.0 + uses: pypa/cibuildwheel@v3.1.4 env: CIBW_BUILD: ${{ matrix.python }} CIBW_ARCHS: ${{ matrix.arch }} @@ -287,9 +291,6 @@ jobs: # ------------------------------------------------------------------- # CPython ARM 64 bits # ------------------------------------------------------------------- - - build: CPython 3.8 ARM 64 bits - python: cp38-macosx_arm64 - arch: arm64 - build: CPython 3.9 ARM 64 bits python: cp39-macosx_arm64 arch: arm64 @@ -305,6 +306,9 @@ jobs: - build: CPython 3.13 ARM 64 bits python: cp313-macosx_arm64 arch: arm64 + - build: CPython 3.14 ARM 64 bits + python: cp314-macosx_arm64 + arch: arm64 steps: - uses: actions/checkout@v4 @@ -312,10 +316,10 @@ jobs: - uses: actions/setup-python@v5 name: Install Python with: - python-version: '3.9' + python-version: '3.11' - name: Build wheels - uses: pypa/cibuildwheel@v2.22.0 + uses: pypa/cibuildwheel@v3.1.4 env: CIBW_BUILD: ${{ matrix.python }} CIBW_ARCHS: ${{ matrix.arch }} @@ -342,9 +346,6 @@ jobs: # ------------------------------------------------------------------- # CPython 64 bits # ------------------------------------------------------------------- - - build: CPython 3.8 64 bits - python: cp38-win_amd64 - arch: AMD64 - build: CPython 3.9 64 bits python: cp39-win_amd64 arch: AMD64 @@ -360,6 +361,9 @@ jobs: - build: CPython 3.13 64 bits python: cp313-win_amd64 arch: AMD64 + - build: CPython 3.14 64 bits + python: cp314-win_amd64 + arch: AMD64 steps: - uses: actions/checkout@v4 @@ -367,10 +371,10 @@ jobs: - uses: actions/setup-python@v5 name: Install Python with: - python-version: '3.9' + python-version: '3.11' - name: Build wheels - uses: pypa/cibuildwheel@v2.22.0 + uses: pypa/cibuildwheel@v3.1.4 env: CIBW_BUILD: ${{ matrix.python }} CIBW_ARCHS: ${{ matrix.arch }} @@ -382,7 +386,7 @@ jobs: upload_pypi: - needs: [sdist, linux, macos, macos-arm, windows] + needs: [sdist, linux, linux-arm, macos, macos-arm, windows] runs-on: ubuntu-latest if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/v') steps: diff --git a/CMakeLists.txt b/CMakeLists.txt index 579255ddd9..9ad6d3c8a7 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,7 @@ set(CMAKE_WARN_DEPRECATED ON) if(APPLE AND NOT DEFINED CMAKE_OSX_DEPLOYMENT_TARGET) # The value of this variable should be set prior to the first project() command invocation # because it may influence configuration of the toolchain and flags. - set(CMAKE_OSX_DEPLOYMENT_TARGET "10.13" CACHE STRING "Minimum OS X deployment version") + set(CMAKE_OSX_DEPLOYMENT_TARGET "10.15" CACHE STRING "Minimum OS X deployment version") endif() diff --git a/docs/quick_start/installation.rst b/docs/quick_start/installation.rst index 03da2f9e9f..d8d208e05c 100644 --- a/docs/quick_start/installation.rst +++ b/docs/quick_start/installation.rst @@ -122,28 +122,28 @@ items manually: Required components: -- C++ 11-17 compiler (gcc, clang, msvc) +- C++ 17-23 compiler (gcc, clang, msvc) - CMake >= 3.14 -- \*Expat >= 2.4.1 (XML parser for CDL/CLF/CTF) -- \*yaml-cpp >= 0.7.0 (YAML parser for Configs) +- \*Expat >= 2.6.0 (XML parser for CDL/CLF/CTF) +- \*yaml-cpp >= 0.8.0 (YAML parser for Configs) - \*Imath >= 3.1.1 (for half domain LUTs) - \*pystring >= 1.1.3 -- \*minizip-ng >= 3.0.7 (for config archiving) +- \*minizip-ng >= 4.0.0 (for config archiving) - \*ZLIB >= 1.2.13 (for config archiving) Optional OCIO functionality also depends on: - \*Little CMS >= 2.2 (for ociobakelut ICC profile baking) - \*OpenGL GLUT & GLEW (for ociodisplay) -- \*OpenEXR >= 3.0.5 (for apps including ocioconvert) +- \*OpenEXR >= 3.2.0 (for apps including ocioconvert) - OpenImageIO >= 2.2.14 (for apps including ocioconvert) - \*OpenFX >= 1.4 (for the OpenFX plug-ins) -- OpenShadingLanguage >= 1.11 (for the OSL unit tests) +- OpenShadingLanguage >= 1.13 (for the OSL unit tests) - Doxygen (for the docs) - NumPy (optionally used in the Python test suite) - \*pybind11 >= 2.9.2 (for the Python binding) -- Python >= 3.7 (for the Python binding only) -- Python 3.7 - 3.9 (for building the documentation) +- Python >= 3.9 (for the Python binding only) +- Python 3.9+ (for building the documentation) Building the documentation requires the following packages, available via PyPI: diff --git a/include/OpenColorIO/OpenColorIO.h b/include/OpenColorIO/OpenColorIO.h index 53ffa8315f..915737fa08 100644 --- a/include/OpenColorIO/OpenColorIO.h +++ b/include/OpenColorIO/OpenColorIO.h @@ -13,6 +13,7 @@ #include #include #include +#include #include "OpenColorABI.h" #include "OpenColorTypes.h" @@ -1095,37 +1096,78 @@ class OCIOEXPORT Config /** * \brief * - * $OCIO_ACTIVE_DISPLAYS envvar can, at runtime, optionally override the - * allowed displays. It is a comma or colon delimited list. Active displays - * that are not in the specified profile will be ignored, and the - * left-most defined display will be the default. - * - * Comma-delimited list of names to filter and order the active displays. + * The Active Displays list allows end users, config authors, and client apps to filter and + * reorder of the list of displays available in a user-interface. The list may be left empty + * to indicate all displays are active. + * + * The first active display is the config's Default Display. + * + * If the active list would remove all displays from a config, it is ignored (though the + * config won't validate). + * + * When serialized in the config, commas are used as separators. However, if a display name + * contains a comma, the name will be enclosed in quotes so its comma is not a separator. * - * \note - * The setter does not override the envvar. The getter does not take into - * account the envvar value and thus may not represent what the user is seeing. + * The OCIO_ACTIVE_DISPLAYS environment variable will override the active list specified in + * the config file as well as any modifications made by the client app. These functions + * only get and set what is in the config object and do not take into account the override + * and thus may not represent the actual user experience. */ + /// Set all active displays at once as a comma or colon delimited string. This replaces any + /// previous contents of the list. void setActiveDisplays(const char * displays); + /// Get a string with all active displays (as it would appear in a config file). + /// Commas are always used as the separator. const char * getActiveDisplays() const; + /// Get the number of active displays. + int getNumActiveDisplays() const; + /// Get a single active display, by index. Returns nullptr if the index is out of range. + const char * getActiveDisplay(int index) const; + /// Add a single active display to the end of the list. If the display is already present, + /// no action is taken. + void addActiveDisplay(const char * display); + /// Remove a single display. Will throw if the display is not present. + void removeActiveDisplay(const char * display); + /// Clear the active displays list. + void clearActiveDisplays(); /** * \brief * - * $OCIO_ACTIVE_VIEWS envvar can, at runtime, optionally override the allowed views. - * It is a comma or colon delimited list. - * Active views that are not in the specified profile will be ignored, and the - * left-most defined view will be the default. - * - * Comma-delimited list of names to filter and order the active views. + * The Active Views list allows end users, config authors, and client apps to filter and + * reorder of the list of views available in a user-interface. The list may be left empty + * to indicate all views are active. + * + * The first active view for a display is its Default View. + * + * If the active list would remove all views from a display, the list is ignored for that + * display and all views are shown for it. + * + * When serialized in the config, commas are used as separators. However, if a view name + * contains a comma, the name will be enclosed in quotes so its comma is not a separator. * - * \note - * The setter does not override the envvar. The getter does not take - * into account the envvar value and thus may not represent what the - * user is seeing. + * The OCIO_ACTIVE_VIEWS environment variable will override the active list specified in + * the config file as well as any modifications made by the client app. These functions + * only get and set what is in the config object and do not take into account the override + * and thus may not represent the actual user experience. */ + /// Set all active views at once as a comma or colon delimited string. This replaces any + /// previous contents of the list. void setActiveViews(const char * views); + /// Get a string with all active views (as it would appear in a config file). + /// Commas are always used as the separator. const char * getActiveViews() const; + /// Get the number of active views. + int getNumActiveViews() const; + /// Get a single active view, by index. Returns nullptr if the index is out of range. + const char * getActiveView(int index) const; + /// Add a single active view to the end of the list. If the view is already present, + /// no action is taken. + void addActiveView(const char * view); + /// Remove a single view. Will throw if the view is not present. + void removeActiveView(const char * view); + /// Clear the active views list. + void clearActiveViews(); /// Get all displays in the config, ignoring the active_displays list. int getNumDisplaysAll() const noexcept; @@ -1984,6 +2026,40 @@ class OCIOEXPORT ColorSpace const char * getDescription() const noexcept; void setDescription(const char * description); + /** + * Get/Set the interop ID for the color space. The interop ID is a + * structured string defined by the Color Interop Forum. It is intended to + * identify color spaces in a way that is portable across different configs, + * making it suitable for use in various file formats. The Color Interop + * Forum publishes ID strings for common color spaces. If you create your + * own IDs, they must be preceded by a namespace string. The setter will + * throw if the string does not follow certain rules (run ociocheck for a + * more complete validation). + */ + const char * getInteropID() const noexcept; + void setInteropID(const char * interopID); + + /** + * Get/Set the interchange attributes. + * + * Currently supported attribute names are "amf_transform_ids" and + * "icc_profile_name". Using any other name will throw. If the attribute is + * not defined, it'll return an empty string. Setting the value to an empty + * string will effectively delete the attribute. + * + * The AMF transform IDs are used to identify specific transforms in the + * ACES Metadata File. Multiple transform IDs can be specified in a + * newline-separated string. + * + * The ICC profile name identifies the ICC color profile associated with + * this color space. This can be used to link OCIO color spaces with + * corresponding ICC profiles for applications that need to work with both + * color management systems. + */ + const char *getInterchangeAttribute(const char *attrName) const; + void setInterchangeAttribute(const char* attrName, const char *value); + std::map getInterchangeAttributes() const noexcept; + BitDepth getBitDepth() const noexcept; void setBitDepth(BitDepth bitDepth); diff --git a/pyproject.toml b/pyproject.toml index 793353ff6d..f5677d1216 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,9 +30,9 @@ test-command = [ before-build = "share/ci/scripts/linux/dnf/install_docs_env.sh" [tool.cibuildwheel.macos] -# cibuildwheel in some cases set this to 10.9 by default, OCIO needs >= 10.13 +# cibuildwheel in some cases set this to 10.9 by default, OCIO needs >= 10.15 # macOS ARM wheels needs 11.0, cibuildwheel will automatically bump where appropriate -environment = { MACOSX_DEPLOYMENT_TARGET="10.13" } +environment = { MACOSX_DEPLOYMENT_TARGET="10.15" } before-build = "share/ci/scripts/macos/install_docs_env.sh" [tool.cibuildwheel.windows] diff --git a/setup.cfg b/setup.cfg index d5af16ae6f..aa1015183a 100644 --- a/setup.cfg +++ b/setup.cfg @@ -7,13 +7,12 @@ classifiers = Topic :: Software Development :: Libraries :: Python Modules Programming Language :: C++ Programming Language :: Python :: 3 - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Programming Language :: Python :: 3.12 Programming Language :: Python :: 3.13 + Programming Language :: Python :: 3.14 Programming Language :: Python :: Implementation :: CPython Programming Language :: C++ description = OpenColorIO (OCIO) is a complete color management solution geared towards motion picture production with an emphasis on visual effects and computer animation. @@ -22,7 +21,7 @@ license_files = LICENSE long_description = file: README.md, LICENSE long_description_content_type = text/markdown name = opencolorio -python_requires = '>=3.7' +python_requires = '>=3.9' url = https://opencolorio.org/ [options] diff --git a/share/ci/scripts/multi/install_yaml-cpp.sh b/share/ci/scripts/multi/install_yaml-cpp.sh index 7a916170bc..7265141379 100755 --- a/share/ci/scripts/multi/install_yaml-cpp.sh +++ b/share/ci/scripts/multi/install_yaml-cpp.sh @@ -23,8 +23,10 @@ if [ "$YAMLCPP_VERSION" == "latest" ]; then else if [[ "$YAMLCPP_MINOR" -lt 6 && "$YAMLCPP_PATCH" -lt 3 ]]; then git checkout tags/release-${YAMLCPP_VERSION} -b release-${YAMLCPP_VERSION} - else + elif [[ "$YAMLCPP_MINOR" -lt 8 ]]; then git checkout tags/yaml-cpp-${YAMLCPP_VERSION} -b yaml-cpp-${YAMLCPP_VERSION} + else + git checkout tags/${YAMLCPP_VERSION} -b ${YAMLCPP_VERSION} fi fi diff --git a/share/cmake/macros/GetPythonPreCommand.cmake b/share/cmake/macros/GetPythonPreCommand.cmake index d5539f8ec6..018692a33d 100644 --- a/share/cmake/macros/GetPythonPreCommand.cmake +++ b/share/cmake/macros/GetPythonPreCommand.cmake @@ -22,6 +22,7 @@ macro(get_python_pre_command) if(WIN32) # Use Windows path separators since this is being passed through to cmd file(TO_NATIVE_PATH ${PROJECT_BINARY_DIR} _WIN_BINARY_DIR) + file(TO_NATIVE_PATH ${PROJECT_SOURCE_DIR} _WIN_SOURCE_DIR) set(_DLL_PATH "${_WIN_BINARY_DIR}\\src\\OpenColorIO") if(MSVC_IDE) @@ -42,15 +43,20 @@ macro(get_python_pre_command) # Build path list set(_WIN_PATHS - ${_PYD_PATH} - "${PROJECT_SOURCE_DIR}\\share\\docs" + ${_PYD_PATH} + "${_WIN_SOURCE_DIR}\\share\\docs" ) # Include optional paths from macro arguments foreach(_PATH ${ARGN}) file(TO_NATIVE_PATH ${_PATH} _WIN_PATH) list(APPEND _WIN_PATHS ${_WIN_PATH}) endforeach() - list(APPEND _WIN_PATHS "%PYTHONPATH%") + + # Double % to escape as the cmd.exe will eat one level of %. That + # results in an empty path in the ENV variable and Python fails to + # convert that to absolute path. Possibly due to + # https://www.cve.news/cve-2023-41105/ + list(APPEND _WIN_PATHS "%%PYTHONPATH%%") string(JOIN "\\\\;" _PYTHONPATH_VALUE ${_WIN_PATHS}) string(CONCAT _PYTHONPATH_SET "PYTHONPATH=${_PYTHONPATH_VALUE}") diff --git a/share/cmake/modules/FindExtPackages.cmake b/share/cmake/modules/FindExtPackages.cmake index aa2550ac76..66c8fa5ffa 100644 --- a/share/cmake/modules/FindExtPackages.cmake +++ b/share/cmake/modules/FindExtPackages.cmake @@ -55,29 +55,29 @@ message(STATUS "Checking for mandatory dependencies...") # expat # https://github.com/libexpat/libexpat ocio_handle_dependency( expat REQUIRED ALLOW_INSTALL - MIN_VERSION 2.4.1 - RECOMMENDED_VERSION 2.5.0 - RECOMMENDED_VERSION_REASON "CVE fixes and fix issue with symbol leakage when built as a static library") + MIN_VERSION 2.6.0 + RECOMMENDED_VERSION 2.7.2 + RECOMMENDED_VERSION_REASON "CVE fixes and Latest version tested with OCIO") # yaml-cpp # https://github.com/jbeder/yaml-cpp ocio_handle_dependency( yaml-cpp REQUIRED ALLOW_INSTALL - MIN_VERSION 0.6.3 - RECOMMENDED_VERSION 0.7.0 + MIN_VERSION 0.8.0 + RECOMMENDED_VERSION 0.8.0 RECOMMENDED_VERSION_REASON "Latest version tested with OCIO") # pystring # https://github.com/imageworks/pystring ocio_handle_dependency( pystring REQUIRED ALLOW_INSTALL MIN_VERSION 1.1.3 - RECOMMENDED_VERSION 1.1.3 + RECOMMENDED_VERSION 1.1.4 RECOMMENDED_VERSION_REASON "Latest version tested with OCIO") -# Imath (>=3.1) +# Imath # https://github.com/AcademySoftwareFoundation/Imath ocio_handle_dependency( Imath REQUIRED ALLOW_INSTALL MIN_VERSION 3.1.1 - RECOMMENDED_VERSION 3.1.12 + RECOMMENDED_VERSION 3.2.1 RECOMMENDED_VERSION_REASON "Latest version tested with OCIO") ############################################################################### @@ -101,9 +101,9 @@ ocio_handle_dependency( Imath REQUIRED ALLOW_INSTALL # See https://nvd.nist.gov/vuln/detail/CVE-2022-37434 # See https://github.com/madler/zlib/releases/tag/v1.2.13 ocio_handle_dependency( ZLIB REQUIRED ALLOW_INSTALL - MIN_VERSION 1.2.8 - RECOMMENDED_VERSION 1.2.13 - RECOMMENDED_VERSION_REASON "CVE fixes" + MIN_VERSION 1.2.13 # CVE fixes + RECOMMENDED_VERSION 1.3.1 + RECOMMENDED_VERSION_REASON "Latest version tested with OCIO" VERSION_VARS ZLIB_VERSION_STRING ZLIB_VERSION ) ############################################################################### @@ -111,8 +111,8 @@ ocio_handle_dependency( ZLIB REQUIRED ALLOW_INSTALL # minizip-ng # https://github.com/zlib-ng/minizip-ng ocio_handle_dependency( minizip-ng REQUIRED ALLOW_INSTALL - MIN_VERSION 3.0.6 - RECOMMENDED_VERSION 3.0.7 + MIN_VERSION 4.0.0 + RECOMMENDED_VERSION 4.0.10 RECOMMENDED_VERSION_REASON "Latest version tested with OCIO") ############################################################################### @@ -133,7 +133,7 @@ if(OCIO_BUILD_APPS) # https://github.com/mm2/Little-CMS ocio_handle_dependency( lcms2 REQUIRED ALLOW_INSTALL MIN_VERSION 2.2 - RECOMMENDED_VERSION 2.2 + RECOMMENDED_VERSION 2.17 RECOMMENDED_VERSION_REASON "Latest version tested with OCIO") endif() @@ -142,7 +142,7 @@ if(OCIO_BUILD_OPENFX) # https://github.com/ofxa/openfx ocio_handle_dependency( openfx REQUIRED ALLOW_INSTALL MIN_VERSION 1.4 - RECOMMENDED_VERSION 1.4 + RECOMMENDED_VERSION 1.5 RECOMMENDED_VERSION_REASON "Latest version tested with OCIO") endif() @@ -179,19 +179,18 @@ if(OCIO_BUILD_PYTHON OR OCIO_BUILD_DOCS) # pybind11 2.9 fixes issues with MS Visual Studio 2022 (Debug). ocio_handle_dependency( pybind11 REQUIRED ALLOW_INSTALL MIN_VERSION 2.9.2 - RECOMMENDED_VERSION 2.12.1) + RECOMMENDED_VERSION 3.0.1) endif() endif() # Set OpenEXR Minimum version as a variable since it it used at multiple places. -set(OpenEXR_MININUM_VERSION "3.0.5") if((OCIO_BUILD_APPS AND OCIO_USE_OIIO_FOR_APPS) OR OCIO_BUILD_TESTS) # OpenImageIO is required for OSL unit test and optional for apps. # OpenImageIO # https://github.com/OpenImageIO/oiio set(OIIO_VERSION "2.2.14") - set(OIIO_RECOMMENDED_VERSION "2.4") + set(OIIO_RECOMMENDED_VERSION "3") # Supported from OIIO 2.4+. Setting this for lower versions doesn't affect anything. set(OPENIMAGEIO_CONFIG_DO_NOT_FIND_IMATH 1) @@ -259,8 +258,8 @@ if(OCIO_BUILD_APPS) ############################################################################### # Calling find_package in CONFIG mode using PREFER_CONFIG option. ocio_handle_dependency( OpenEXR PREFER_CONFIG ALLOW_INSTALL - MIN_VERSION ${OpenEXR_MININUM_VERSION} - RECOMMENDED_VERSION 3.1.5 + MIN_VERSION 3.2.0 + RECOMMENDED_VERSION 3.4 RECOMMENDED_VERSION_REASON "Latest version tested with OCIO" PROMOTE_TARGET OpenEXR::OpenEXR) @@ -285,8 +284,8 @@ if(OCIO_BUILD_TESTS) # OpenShadingLanguage # https://github.com/AcademySoftwareFoundation/OpenShadingLanguage ocio_handle_dependency( OSL - MIN_VERSION 1.11 - RECOMMENDED_VERSION 1.11 + MIN_VERSION 1.13 + RECOMMENDED_VERSION 1.14 RECOMMENDED_VERSION_REASON "Latest version tested with OCIO") if(NOT OSL_FOUND) message(WARNING "Skipping build of the OpenShadingLanguage unit tests (OSL missing)") diff --git a/share/cmake/modules/Findyaml-cpp.cmake b/share/cmake/modules/Findyaml-cpp.cmake index 110ae2e4ca..d10bdb4f5e 100644 --- a/share/cmake/modules/Findyaml-cpp.cmake +++ b/share/cmake/modules/Findyaml-cpp.cmake @@ -12,10 +12,6 @@ # Global targets defined by this module: # yaml-cpp::yaml-cpp # -# For compatibility with the upstream CMake package, the following variables and targets are defined: -# YAML_CPP_LIBRARIES - Libraries to link against yaml-cpp -# YAML_CPP_INCLUDE_DIR - Include directory -# # Usually CMake will use the dynamic library rather than static, if both are present. # In this case, you may set yaml-cpp_STATIC_LIBRARY to ON to request use of the static one. # If only the static library is present (such as when OCIO builds the dependency), then the option @@ -163,10 +159,3 @@ if (yaml-cpp_FOUND AND NOT TARGET yaml-cpp::yaml-cpp) add_library(yaml-cpp ALIAS yaml-cpp::yaml-cpp) endif () endif () - -if (yaml-cpp_FOUND) - # TODO: Remove this variable and use the `yaml-cpp::yaml-cpp` target - # directly when the minimum version of yaml-cpp is updated to 0.8. - get_target_property(YAML_CPP_INCLUDE_DIR yaml-cpp::yaml-cpp INCLUDE_DIRECTORIES) - set(YAML_CPP_LIBRARIES yaml-cpp::yaml-cpp) -endif () diff --git a/share/cmake/modules/install/InstallOpenEXR.cmake b/share/cmake/modules/install/InstallOpenEXR.cmake index be9a708729..7fde736eed 100644 --- a/share/cmake/modules/install/InstallOpenEXR.cmake +++ b/share/cmake/modules/install/InstallOpenEXR.cmake @@ -110,7 +110,9 @@ if(NOT OpenEXR_FOUND AND OCIO_INSTALL_EXT_PACKAGES AND NOT OCIO_INSTALL_EXT_PACK -DBUILD_SHARED_LIBS=OFF -DBUILD_TESTING=OFF -DOPENEXR_INSTALL_EXAMPLES=OFF + -DOPENEXR_BUILD_EXAMPLES=OFF -DOPENEXR_BUILD_TOOLS=OFF + -DOPENEXR_FORCE_INTERNAL_DEFLATE=ON # Try to use in-source built Imath first, if available. -DCMAKE_PREFIX_PATH=${_EXT_DIST_ROOT} ) @@ -177,6 +179,13 @@ if(NOT OpenEXR_FOUND AND OCIO_INSTALL_EXT_PACKAGES AND NOT OCIO_INSTALL_EXT_PACK add_dependencies(OpenEXR::OpenEXR openexr_install) + # When building Imath ourselves, make sure it has been built first + # so that OpenEXR can find it. Otherwise OpenEXR will build its + # own copy of Imath which might result in version conflicts. + if (TARGET imath_install) + add_dependencies(openexr_install imath_install) + endif() + if(OCIO_VERBOSE) message(STATUS "Installing OpenEXR: ${OpenEXR_LIBRARY} (version \"${OpenEXR_VERSION}\")") endif() @@ -209,7 +218,7 @@ if(_OpenEXR_TARGET_CREATE) set_target_properties(OpenEXR::OpenEXR PROPERTIES IMPORTED_LOCATION ${OpenEXR_LIBRARY} INTERFACE_INCLUDE_DIRECTORIES "${OpenEXR_INCLUDE_DIR}" - INTERFACE_LINK_LIBRARIES "OpenEXR::IlmThreadConfig;Imath::Imath;OpenEXR::IlmThreadConfig;OpenEXR::Iex;OpenEXR::IlmThread;ZLIB::ZLIB" + INTERFACE_LINK_LIBRARIES "OpenEXR::IlmThreadConfig;Imath::Imath;OpenEXR::IlmThreadConfig;OpenEXR::Iex;OpenEXR::IlmThread;OpenEXR::OpenEXRCore" ) set_target_properties(OpenEXR::OpenEXRConfig PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${OpenEXR_INCLUDE_DIR};${OpenEXR_INCLUDE_DIR}/OpenEXR" @@ -217,13 +226,13 @@ if(_OpenEXR_TARGET_CREATE) set_target_properties(OpenEXR::OpenEXRCore PROPERTIES IMPORTED_LOCATION ${OpenEXRCore_LIBRARY} INTERFACE_INCLUDE_DIRECTORIES "${OpenEXR_INCLUDE_DIR}" - INTERFACE_LINK_LIBRARIES "OpenEXR::IlmThreadConfig;ZLIB::ZLIB;\$" + INTERFACE_LINK_LIBRARIES "OpenEXR::IlmThreadConfig;Imath::Imath" STATIC_LIBRARY_OPTIONS "-no_warning_for_no_symbols" ) set_target_properties(OpenEXR::OpenEXRUtil PROPERTIES IMPORTED_LOCATION ${OpenEXRUtil_LIBRARY} INTERFACE_INCLUDE_DIRECTORIES "${OpenEXR_INCLUDE_DIR}" - INTERFACE_LINK_LIBRARIES "OpenEXR::IlmThreadConfig;OpenEXR::OpenEXR" + INTERFACE_LINK_LIBRARIES "OpenEXR::IlmThreadConfig;OpenEXR::OpenEXR;OpenEXR::OpenEXRCore" ) mark_as_advanced(OpenEXR_INCLUDE_DIR OpenEXR_LIBRARY OpenEXR_VERSION) diff --git a/share/cmake/modules/install/Installminizip-ng.cmake b/share/cmake/modules/install/Installminizip-ng.cmake index 19c54a950a..28b6e6a782 100644 --- a/share/cmake/modules/install/Installminizip-ng.cmake +++ b/share/cmake/modules/install/Installminizip-ng.cmake @@ -45,7 +45,12 @@ if(NOT minizip-ng_FOUND AND OCIO_INSTALL_EXT_PACKAGES AND NOT OCIO_INSTALL_EXT_P set(minizip-ng_INCLUDE_DIR "${_EXT_DIST_ROOT}/${CMAKE_INSTALL_INCLUDEDIR}/minizip-ng") # Minizip-ng use a hardcoded lib prefix instead of CMAKE_STATIC_LIBRARY_PREFIX - set(_minizip-ng_LIB_PREFIX "lib") + # Fixed from 4.0.7, see https://github.com/zlib-ng/minizip-ng/issues/778 + if(${minizip-ng_VERSION} VERSION_GREATER_EQUAL "4.0.7") + set(_minizip-ng_LIB_PREFIX "${CMAKE_STATIC_LIBRARY_PREFIX}") + else() + set(_minizip-ng_LIB_PREFIX "lib") + endif() set(minizip-ng_LIBRARY "${_EXT_DIST_ROOT}/${CMAKE_INSTALL_LIBDIR}/${_minizip-ng_LIB_PREFIX}minizip-ng${_minizip-ng_LIB_SUFFIX}${CMAKE_STATIC_LIBRARY_SUFFIX}") @@ -63,11 +68,6 @@ if(NOT minizip-ng_FOUND AND OCIO_INSTALL_EXT_PACKAGES AND NOT OCIO_INSTALL_EXT_P -DCMAKE_INSTALL_BINDIR=${CMAKE_INSTALL_BINDIR} -DCMAKE_INSTALL_DATADIR=${CMAKE_INSTALL_DATADIR} -DCMAKE_INSTALL_LIBDIR=${CMAKE_INSTALL_LIBDIR} - # Since the other modules create a subfolder for the includes by default and since - # minizip-ng does not, a suffix is added to CMAKE_INSTALL_INCLUDEDIR in order to - # install the headers under a subdirectory named "minizip-ng". - # Note that this does not affect external builds for minizip-ng. - -DCMAKE_INSTALL_INCLUDEDIR=${CMAKE_INSTALL_INCLUDEDIR}/minizip-ng -DCMAKE_OBJECT_PATH_MAX=${CMAKE_OBJECT_PATH_MAX} -DBUILD_SHARED_LIBS=OFF -DMZ_OPENSSL=OFF diff --git a/share/cmake/modules/install/Installopenfx.cmake b/share/cmake/modules/install/Installopenfx.cmake index be454de4c2..8ce587b33f 100644 --- a/share/cmake/modules/install/Installopenfx.cmake +++ b/share/cmake/modules/install/Installopenfx.cmake @@ -50,9 +50,11 @@ if(NOT openfx_FOUND AND OCIO_INSTALL_EXT_PACKAGES AND NOT OCIO_INSTALL_EXT_PACKA ExternalProject_Add(openfx_install GIT_REPOSITORY "https://github.com/ofxa/openfx.git" - # The latest version from 2015 is OFX_Release_1_4_TAG. + # The latest version from 2015 is OFX_Release_1_4_TAG. + # The latest version from 2024 is OFX_Release_1.5. # Need to be careful since older version might have the patch number in the tag. - GIT_TAG "OFX_Release_${openfx_VERSION_MAJOR}_${openfx_VERSION_MINOR}_TAG" + # There don't seem to be enough consistency in tag names that we can rely on. + GIT_TAG "OFX_Release_${openfx_VERSION_MAJOR}.${openfx_VERSION_MINOR}" GIT_CONFIG advice.detachedHead=false GIT_SHALLOW TRUE PREFIX "${_EXT_BUILD_ROOT}/openfx" diff --git a/share/cmake/modules/install/Installpybind11.cmake b/share/cmake/modules/install/Installpybind11.cmake index a0ae71f151..f432d5289e 100644 --- a/share/cmake/modules/install/Installpybind11.cmake +++ b/share/cmake/modules/install/Installpybind11.cmake @@ -120,5 +120,13 @@ if(_pybind11_TARGET_CREATE) INTERFACE_INCLUDE_DIRECTORIES ${pybind11_INCLUDE_DIR} ) + # See pybind11Common.cmake for reasoning + if (MSVC) + set_target_properties(pybind11::module PROPERTIES + INTERFACE_COMPILE_OPTIONS /bigobj + ) + + endif() + mark_as_advanced(pybind11_INCLUDE_DIR pybind11_VERSION) endif() diff --git a/share/cmake/modules/install/Installyaml-cpp.cmake b/share/cmake/modules/install/Installyaml-cpp.cmake index 6353a4d1d1..3ec993969c 100644 --- a/share/cmake/modules/install/Installyaml-cpp.cmake +++ b/share/cmake/modules/install/Installyaml-cpp.cmake @@ -10,12 +10,7 @@ # yaml-cpp_VERSION - Library's version # # Global targets defined by this module: -# yaml-cpp::yaml-cpp -# -# For compatibility with the upstream CMake package, the following variables and targets are defined: -# yaml-cpp::yaml-cpp - Alias of the yaml-cpp target -# YAML_CPP_LIBRARIES - Libraries to link against yaml-cpp -# YAML_CPP_INCLUDE_DIR - Include directory +# yaml-cpp::yaml-cpp # ############################################################################### @@ -118,7 +113,8 @@ if(NOT yaml-cpp_FOUND AND OCIO_INSTALL_EXT_PACKAGES AND NOT OCIO_INSTALL_EXT_PAC -DANDROID_STL=${ANDROID_STL}) endif() - set(yaml-cpp_GIT_TAG "yaml-cpp-${yaml-cpp_VERSION}") + # in v0.8.0 yaml switched from "yaml-cpp-vA.B.C" to "vA.B.C" format for tags. + set(yaml-cpp_GIT_TAG "${yaml-cpp_VERSION}") # Hack to let imported target be built from ExternalProject_Add file(MAKE_DIRECTORY ${yaml-cpp_INCLUDE_DIR}) @@ -154,10 +150,9 @@ if(_yaml-cpp_TARGET_CREATE) set_target_properties(yaml-cpp::yaml-cpp PROPERTIES IMPORTED_LOCATION ${yaml-cpp_LIBRARY} INTERFACE_INCLUDE_DIRECTORIES ${yaml-cpp_INCLUDE_DIR} + # https://github.com/jbeder/yaml-cpp/issues/1339 + INTERFACE_COMPILE_DEFINITIONS YAML_CPP_STATIC_DEFINE ) mark_as_advanced(yaml-cpp_INCLUDE_DIR yaml-cpp_LIBRARY yaml-cpp_VERSION) endif() - -set(YAML_CPP_INCLUDE_DIR "${yaml-cpp_INCLUDE_DIR}") -set(YAML_CPP_LIBRARIES yaml-cpp::yaml-cpp) diff --git a/share/cmake/utils/CompilerFlags.cmake b/share/cmake/utils/CompilerFlags.cmake index d87e73f2d4..3e609ef6a6 100644 --- a/share/cmake/utils/CompilerFlags.cmake +++ b/share/cmake/utils/CompilerFlags.cmake @@ -60,12 +60,13 @@ if(USE_MSVC) # /we4062 Enables warning in switch when an enumeration value is not explicitly handled. set(PLATFORM_COMPILE_OPTIONS "${PLATFORM_COMPILE_OPTIONS};/EHsc;/DWIN32;/we4062") - if(${CMAKE_CXX_STANDARD} GREATER_EQUAL 17) - # Inheriting from std::iterator is deprecated starting with C++17 and Yaml 0.6.3 does that. - set(PLATFORM_COMPILE_OPTIONS - "${PLATFORM_COMPILE_OPTIONS};/D_SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING" - ) - endif() + # seems no longer needed, there were c++ modernization on 0.7.0. /coz +# if(${CMAKE_CXX_STANDARD} GREATER_EQUAL 17) +# # Inheriting from std::iterator is deprecated starting with C++17 and Yaml 0.6.3 does that. +# set(PLATFORM_COMPILE_OPTIONS +# "${PLATFORM_COMPILE_OPTIONS};/D_SILENCE_CXX17_ITERATOR_BASE_CLASS_DEPRECATION_WARNING" +# ) +# endif() # Make MSVC compiler report correct __cplusplus version (otherwise reports 199711L) set(PLATFORM_COMPILE_OPTIONS "${PLATFORM_COMPILE_OPTIONS};/Zc:__cplusplus") diff --git a/share/cmake/utils/CppVersion.cmake b/share/cmake/utils/CppVersion.cmake index 175d89c206..127ec9e7f1 100644 --- a/share/cmake/utils/CppVersion.cmake +++ b/share/cmake/utils/CppVersion.cmake @@ -5,12 +5,12 @@ ############################################################################### # C++ version configuration -set(SUPPORTED_CXX_STANDARDS 11 14 17) +set(SUPPORTED_CXX_STANDARDS 17 20 23) string(REPLACE ";" ", " SUPPORTED_CXX_STANDARDS_STR "${SUPPORTED_CXX_STANDARDS}") if(NOT DEFINED CMAKE_CXX_STANDARD) - message(STATUS "Setting C++ version to '14' as none was specified.") - set(CMAKE_CXX_STANDARD 14 CACHE STRING "C++ standard to compile against") + message(STATUS "Setting C++ version to '17' as none was specified.") + set(CMAKE_CXX_STANDARD 17 CACHE STRING "C++ standard to compile against") elseif(NOT CMAKE_CXX_STANDARD IN_LIST SUPPORTED_CXX_STANDARDS) message(WARNING "CMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD} is unsupported. Supported standards are: ${SUPPORTED_CXX_STANDARDS_STR}.") @@ -29,48 +29,45 @@ else() set(CUSTOM_CXX_FLAGS "-w") endif() -if(${CMAKE_CXX_STANDARD} EQUAL 11) +if(${CMAKE_CXX_STANDARD} EQUAL 17) if(USE_MSVC) - CHECK_CXX_COMPILER_FLAG("${CUSTOM_CXX_FLAGS} -std:c++11" COMPILER_SUPPORTS_CXX11) + CHECK_CXX_COMPILER_FLAG("${CUSTOM_CXX_FLAGS} -std:c++17" COMPILER_SUPPORTS_CXX17) else() - CHECK_CXX_COMPILER_FLAG("${CUSTOM_CXX_FLAGS} -std=c++11" COMPILER_SUPPORTS_CXX11) + CHECK_CXX_COMPILER_FLAG("${CUSTOM_CXX_FLAGS} -std=c++17" COMPILER_SUPPORTS_CXX17) endif() - if(NOT COMPILER_SUPPORTS_CXX11) - message(STATUS - "The compiler ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION} has no C++11 only support. Use C++14.") - set(CMAKE_CXX_STANDARD 14) + if(NOT COMPILER_SUPPORTS_CXX17) + message(FATAL_ERROR + "The compiler ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION} has no C++17 support.") endif() endif() -if(${CMAKE_CXX_STANDARD} EQUAL 14) +if(${CMAKE_CXX_STANDARD} EQUAL 20) if(USE_MSVC) - CHECK_CXX_COMPILER_FLAG("${CUSTOM_CXX_FLAGS} -std:c++14" COMPILER_SUPPORTS_CXX14) + CHECK_CXX_COMPILER_FLAG("${CUSTOM_CXX_FLAGS} -std:c++20" COMPILER_SUPPORTS_CXX20) else() - CHECK_CXX_COMPILER_FLAG("${CUSTOM_CXX_FLAGS} -std=c++14" COMPILER_SUPPORTS_CXX14) + CHECK_CXX_COMPILER_FLAG("${CUSTOM_CXX_FLAGS} -std=c++20" COMPILER_SUPPORTS_CXX20) endif() - if(NOT COMPILER_SUPPORTS_CXX14) - message(STATUS - "The compiler ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION} has no C++14 only support. Use C++17.") - set(CMAKE_CXX_STANDARD 17) + if(NOT COMPILER_SUPPORTS_CXX20) + message(FATAL_ERROR + "The compiler ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION} has no C++20 support.") endif() endif() -if(${CMAKE_CXX_STANDARD} EQUAL 17) +if(${CMAKE_CXX_STANDARD} EQUAL 23) if(USE_MSVC) - CHECK_CXX_COMPILER_FLAG("${CUSTOM_CXX_FLAGS} -std:c++17" COMPILER_SUPPORTS_CXX17) + CHECK_CXX_COMPILER_FLAG("${CUSTOM_CXX_FLAGS} -std:c++23" COMPILER_SUPPORTS_CXX23) else() - CHECK_CXX_COMPILER_FLAG("${CUSTOM_CXX_FLAGS} -std=c++17" COMPILER_SUPPORTS_CXX17) + CHECK_CXX_COMPILER_FLAG("${CUSTOM_CXX_FLAGS} -std=c++23" COMPILER_SUPPORTS_CXX23) endif() - if(NOT COMPILER_SUPPORTS_CXX17) + if(NOT COMPILER_SUPPORTS_CXX23) message(FATAL_ERROR - "The compiler ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION} has no C++17 support.") + "The compiler ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION} has no C++23 support.") endif() endif() - # Disable fallback to other C++ version if standard is not supported. set(CMAKE_CXX_STANDARD_REQUIRED ON) diff --git a/src/OpenColorIO/Baker.cpp b/src/OpenColorIO/Baker.cpp index b8a78a958c..e92a4f3d31 100755 --- a/src/OpenColorIO/Baker.cpp +++ b/src/OpenColorIO/Baker.cpp @@ -126,7 +126,7 @@ void Baker::setFormat(const char * formatName) { if (formatInfoVec[i].capabilities & FORMAT_CAPABILITY_BAKE) { - getImpl()->m_formatName = formatName; + getImpl()->m_formatName = formatName ? formatName : ""; return; } } @@ -155,7 +155,7 @@ FormatMetadata & Baker::getFormatMetadata() void Baker::setInputSpace(const char * inputSpace) { - getImpl()->m_inputSpace = inputSpace; + getImpl()->m_inputSpace = inputSpace ? inputSpace : ""; } const char * Baker::getInputSpace() const @@ -165,7 +165,7 @@ const char * Baker::getInputSpace() const void Baker::setShaperSpace(const char * shaperSpace) { - getImpl()->m_shaperSpace = shaperSpace; + getImpl()->m_shaperSpace = shaperSpace ? shaperSpace : ""; } const char * Baker::getShaperSpace() const @@ -175,7 +175,7 @@ const char * Baker::getShaperSpace() const void Baker::setLooks(const char * looks) { - getImpl()->m_looks = looks; + getImpl()->m_looks = looks ? looks : ""; } const char * Baker::getLooks() const @@ -185,7 +185,7 @@ const char * Baker::getLooks() const void Baker::setTargetSpace(const char * targetSpace) { - getImpl()->m_targetSpace = targetSpace; + getImpl()->m_targetSpace = targetSpace ? targetSpace : ""; } const char * Baker::getTargetSpace() const @@ -205,7 +205,7 @@ const char * Baker::getView() const void Baker::setDisplayView(const char * display, const char * view) { - if (!display ^ !view) + if (!display || !view) { throw Exception("Both display and view must be set."); } diff --git a/src/OpenColorIO/CMakeLists.txt b/src/OpenColorIO/CMakeLists.txt index 7d2894da6b..ebef9328a3 100755 --- a/src/OpenColorIO/CMakeLists.txt +++ b/src/OpenColorIO/CMakeLists.txt @@ -315,7 +315,7 @@ target_link_libraries(OpenColorIO "$" "$" "$" - ${YAML_CPP_LIBRARIES} + yaml-cpp::yaml-cpp MINIZIP::minizip-ng ) diff --git a/src/OpenColorIO/ColorSpace.cpp b/src/OpenColorIO/ColorSpace.cpp index 4b7ecb25d0..98f776fe18 100644 --- a/src/OpenColorIO/ColorSpace.cpp +++ b/src/OpenColorIO/ColorSpace.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include @@ -13,6 +14,13 @@ #include "utils/StringUtils.h" +namespace +{ +const std::array knownInterchangeNames = { + "amf_transform_ids", + "icc_profile_name" }; +} + namespace OCIO_NAMESPACE { @@ -24,7 +32,9 @@ class ColorSpace::Impl std::string m_equalityGroup; std::string m_description; std::string m_encoding; + std::string m_interopID; StringUtils::StringVec m_aliases; + std::map m_interchangeAttribs; BitDepth m_bitDepth{ BIT_DEPTH_UNKNOWN }; bool m_isData{ false }; @@ -62,6 +72,8 @@ class ColorSpace::Impl m_equalityGroup = rhs.m_equalityGroup; m_description = rhs.m_description; m_encoding = rhs.m_encoding; + m_interopID = rhs.m_interopID; + m_interchangeAttribs= rhs.m_interchangeAttribs; m_bitDepth = rhs.m_bitDepth; m_isData = rhs.m_isData; m_referenceSpaceType = rhs.m_referenceSpaceType; @@ -195,7 +207,7 @@ const char * ColorSpace::getFamily() const noexcept void ColorSpace::setFamily(const char * family) { - getImpl()->m_family = family; + getImpl()->m_family = family ? family : ""; } const char * ColorSpace::getEqualityGroup() const noexcept @@ -205,7 +217,7 @@ const char * ColorSpace::getEqualityGroup() const noexcept void ColorSpace::setEqualityGroup(const char * equalityGroup) { - getImpl()->m_equalityGroup = equalityGroup; + getImpl()->m_equalityGroup = equalityGroup ? equalityGroup : ""; } const char * ColorSpace::getDescription() const noexcept @@ -215,7 +227,125 @@ const char * ColorSpace::getDescription() const noexcept void ColorSpace::setDescription(const char * description) { - getImpl()->m_description = description; + getImpl()->m_description = description ? description : ""; +} + +const char * ColorSpace::getInteropID() const noexcept +{ + return getImpl()->m_interopID.c_str(); +} + +void ColorSpace::setInteropID(const char * interopID) +{ + std::string id = interopID ? interopID : ""; + + if (!id.empty()) + { + // check if it only uses ASCII characters: 0-9, a-z, and the following characters (no spaces): + // . - _ ~ / * # % ^ + ( ) [ ] | + auto allowed = [](char c) + { + return (c >= '0' && c <= '9')|| + (c >= 'a' && c <= 'z')|| + c=='.'||c=='-'||c=='_'||c=='~'||c=='/'||c=='*'||c=='#'||c=='%'|| + c=='^'||c=='+'||c=='('||c==')'||c=='['||c==']'||c=='|'||c==':'; + }; + + if (!std::all_of(id.begin(), id.end(), allowed)) + { + std::ostringstream oss; + oss << "InteropID '" << id << "' contains invalid characters. " + "Only lowercase a-z, 0-9 and . - _ ~ / * # % ^ + ( ) [ ] | are allowed." << + std::endl; + throw Exception(oss.str().c_str()); + } + + // Check if has a namespace. + size_t pos = id.find(':'); + if (pos != std::string::npos) + { + // Namespace found, split into namespace and color space. + std::string ns = id.substr(0, pos); + std::string cs = id.substr(pos+1); + + // both should be non-empty + if (ns.empty() || cs.empty()) + { + std::ostringstream oss; + oss << "InteropID '" << id << "' is not valid. " + "If ':' is used, both the namespace and the color space parts must be non-empty." << + std::endl; + throw Exception(oss.str().c_str()); + } + + // More than one ':' is an error. + if (cs.find(':') != std::string::npos) + { + std::ostringstream oss; + oss << "ERROR: InteropID '" << id << "' is not valid. " + "Only one ':' is allowed to separate the namespace and the color space." << + std::endl; + throw Exception(oss.str().c_str()); + } + } + } + + getImpl()->m_interopID = id; +} + +const char * ColorSpace::getInterchangeAttribute(const char* attrName) const +{ + std::string name = attrName ? attrName : ""; + + for (auto& key : knownInterchangeNames) + { + // do case-insensitive comparison. + if (StringUtils::Compare(key, name)) + { + auto it = m_impl->m_interchangeAttribs.find(key); + if (it != m_impl->m_interchangeAttribs.end()) + { + return it->second.c_str(); + } + return ""; + } + } + + std::ostringstream oss; + oss << "Unknown attribute name '" << name << "'."; + throw Exception(oss.str().c_str()); +} + +void ColorSpace::setInterchangeAttribute(const char* attrName, const char* value) +{ + std::string name = attrName ? attrName : ""; + + for (auto& key : knownInterchangeNames) + { + // Do case-insensitive comparison. + if (StringUtils::Compare(key, name)) + { + // use key instead of name for storing in correct capitalization. + if (!value || !*value) + { + m_impl->m_interchangeAttribs.erase(key); + } + else + { + m_impl->m_interchangeAttribs[key] = value; + } + return; + } + } + + std::ostringstream oss; + oss << "Unknown attribute name '" << name << "'."; + throw Exception(oss.str().c_str()); +} + +std::map ColorSpace::getInterchangeAttributes() const noexcept +{ + return m_impl->m_interchangeAttribs; } BitDepth ColorSpace::getBitDepth() const noexcept @@ -265,7 +395,7 @@ const char * ColorSpace::getEncoding() const noexcept void ColorSpace::setEncoding(const char * encoding) { - getImpl()->m_encoding = encoding; + getImpl()->m_encoding = encoding ? encoding : ""; } bool ColorSpace::isData() const noexcept @@ -371,7 +501,6 @@ std::ostream & operator<< (std::ostream & os, const ColorSpace & cs) break; } os << "name=" << cs.getName() << ", "; - std::string str{ cs.getFamily() }; const auto numAliases = cs.getNumAliases(); if (numAliases == 1) { @@ -386,6 +515,15 @@ std::ostream & operator<< (std::ostream & os, const ColorSpace & cs) } os << "], "; } + + std::string str; + + str = cs.getInteropID(); + if (!str.empty()) + { + os << "interop_id=" << str << ", "; + } + str = cs.getFamily(); if (!str.empty()) { os << "family=" << str << ", "; @@ -429,6 +567,10 @@ std::ostream & operator<< (std::ostream & os, const ColorSpace & cs) { os << ", description=" << str; } + for (const auto& attr : cs.getInterchangeAttributes()) + { + os << ", " << attr.first << "=" << attr.second; + } if(cs.getTransform(COLORSPACE_DIR_TO_REFERENCE)) { os << ",\n " << cs.getName() << " --> Reference"; diff --git a/src/OpenColorIO/Config.cpp b/src/OpenColorIO/Config.cpp index ad19b39c79..1a8f819593 100644 --- a/src/OpenColorIO/Config.cpp +++ b/src/OpenColorIO/Config.cpp @@ -247,7 +247,7 @@ static constexpr unsigned LastSupportedMajorVersion = OCIO_VERSION_MAJOR; // For each major version keep the most recent minor. static const unsigned int LastSupportedMinorVersion[] = {0, // Version 1 - 4 // Version 2 + 5 // Version 2 }; } // namespace @@ -1029,6 +1029,7 @@ class Config::Impl oss << "Profile description: " << monitorDescription; cs->setDescription(oss.str().c_str()); cs->setTransform(file, COLORSPACE_DIR_FROM_REFERENCE); + cs->setEncoding("sdr-video"); // Note that it adds it or updates the existing one. m_allColorSpaces->addColorSpace(cs); @@ -1185,7 +1186,7 @@ ConstConfigRcPtr Config::CreateFromFile(const char * filename) // Check if it is an OCIOZ archive. if (magicNumber[0] == 'P' && magicNumber[1] == 'K') { - // Closing ifstream even though it should be close by ifstream deconstructor (RAII). + // Closing ifstream even though it should be closed by ifstream destructor (RAII). ifstream.close(); // The file should be an OCIOZ archive file. @@ -1450,6 +1451,22 @@ void Config::validate() const throw Exception(getImpl()->m_validationtext.c_str()); } + // Make sure that all used interopIDs are available in this config. + const char* interop = cs->getInteropID(); + if(interop && *interop) + { + if(!getColorSpace(interop)) + { + std::ostringstream os; + os << "Config failed color space validation. "; + os << "The color space '" << name << "' "; + os << "refers to an interop ID, '" << interop << "', "; + os << "which is not defined in this config."; + getImpl()->m_validationtext = os.str(); + throw Exception(getImpl()->m_validationtext.c_str()); + } + } + // AddColorSpace, addNamedTransform & setRole already check there is no name & alias // conflict. @@ -1499,7 +1516,8 @@ void Config::validate() const // Check for interchange roles requirements - scene-referred and display-referred. - if (getMajorVersion() >= 2 && getMinorVersion() >= 2) + unsigned int versionHex = (getMajorVersion() << 24) | (getMinorVersion() << 16); + if (versionHex >= 0x02020000u) // v2.2 or higher { bool hasRoleSceneLinear = false; bool hasRoleCompositingLog = false; @@ -3369,7 +3387,7 @@ void Config::removeSharedView(const char * view) { std::ostringstream os; os << "Shared view could not be removed from config. A shared view named '" - << view << "' could be be found."; + << view << "' could not be found."; throw Exception(os.str().c_str()); } } @@ -4098,6 +4116,14 @@ void Config::setActiveDisplays(const char * displays) getImpl()->m_activeDisplays.clear(); getImpl()->m_activeDisplays = SplitStringEnvStyle(displays); + // SplitStringEnvStyle needs to always return a result, even if empty, for look parsing. + // However, this does not count as an active display, so delete it. + if( getImpl()->m_activeDisplays.size() == 1 && + getImpl()->m_activeDisplays[0].empty() ) + { + getImpl()->m_activeDisplays.clear(); + } + getImpl()->m_displayCache.clear(); AutoMutex lock(getImpl()->m_cacheidMutex); @@ -4110,11 +4136,94 @@ const char * Config::getActiveDisplays() const return getImpl()->m_activeDisplaysStr.c_str(); } +void Config::addActiveDisplay(const char * display) +{ + if( !display || !display[0] ) + { + throw Exception("Active display could not be added to config, display name was empty"); + } + + auto it = std::find(getImpl()->m_activeDisplays.begin(), + getImpl()->m_activeDisplays.end(), display); + + if( it != getImpl()->m_activeDisplays.end() ) + { + // Display is already present. + return; + } + + getImpl()->m_activeDisplays.push_back(display); + + getImpl()->m_displayCache.clear(); + AutoMutex lock(getImpl()->m_cacheidMutex); + getImpl()->resetCacheIDs(); +} + +void Config::removeActiveDisplay(const char * display) +{ + if( !display || !display[0] ) + { + throw Exception("Active display could not be removed from config, display name was empty."); + } + + auto it = std::find( getImpl()->m_activeDisplays.begin(), + getImpl()->m_activeDisplays.end(), display ); + + if( it != getImpl()->m_activeDisplays.end() ) + { + getImpl()->m_activeDisplays.erase(it); + } + else + { + std::ostringstream os; + os << "Active display could not be removed from config, display '" + << display << "' was not found."; + throw Exception(os.str().c_str()); + } + + getImpl()->m_displayCache.clear(); + AutoMutex lock(getImpl()->m_cacheidMutex); + getImpl()->resetCacheIDs(); +} + +void Config::clearActiveDisplays() +{ + getImpl()->m_activeDisplays.clear(); + + getImpl()->m_displayCache.clear(); + AutoMutex lock(getImpl()->m_cacheidMutex); + getImpl()->resetCacheIDs(); +} + +const char * Config::getActiveDisplay( int index ) const +{ + if( index<0 || + index >= static_cast(getImpl()->m_activeDisplays.size())) + { + return nullptr; + } + + return getImpl()->m_activeDisplays[index].c_str(); +} + +int Config::getNumActiveDisplays() const +{ + return static_cast(getImpl()->m_activeDisplays.size()); +} + void Config::setActiveViews(const char * views) { getImpl()->m_activeViews.clear(); getImpl()->m_activeViews = SplitStringEnvStyle(views); + // SplitStringEnvStyle needs to always return a result, even if empty, for look parsing. + // However, this does not count as an active view, so delete it. + if( getImpl()->m_activeViews.size() == 1 && + getImpl()->m_activeViews[0].empty() ) + { + getImpl()->m_activeViews.clear(); + } + getImpl()->m_displayCache.clear(); AutoMutex lock(getImpl()->m_cacheidMutex); @@ -4127,6 +4236,81 @@ const char * Config::getActiveViews() const return getImpl()->m_activeViewsStr.c_str(); } +void Config::addActiveView(const char * view) +{ + if( !view || !view[0] ) + { + throw Exception("Active view could not be added to config, view name was empty."); + } + + auto it = std::find(getImpl()->m_activeViews.begin(), + getImpl()->m_activeViews.end(), view); + + if( it != getImpl()->m_activeViews.end() ) + { + // View is already present. + return; + } + + getImpl()->m_activeViews.push_back(view); + + getImpl()->m_displayCache.clear(); + AutoMutex lock(getImpl()->m_cacheidMutex); + getImpl()->resetCacheIDs(); +} + +void Config::removeActiveView(const char * view) +{ + if( !view || !view[0] ) + { + throw Exception("Active view could not be removed from config, view name was empty."); + } + + auto it = std::find( getImpl()->m_activeViews.begin(), + getImpl()->m_activeViews.end(), view ); + + if(it!=getImpl()->m_activeViews.end()) + { + getImpl()->m_activeViews.erase(it); + } + else + { + std::ostringstream os; + os << "Active view could not be removed from config, view '" + << view << "' was not found."; + throw Exception(os.str().c_str()); + } + + getImpl()->m_displayCache.clear(); + AutoMutex lock(getImpl()->m_cacheidMutex); + getImpl()->resetCacheIDs(); +} + +void Config::clearActiveViews() +{ + getImpl()->m_activeViews.clear(); + + getImpl()->m_displayCache.clear(); + AutoMutex lock(getImpl()->m_cacheidMutex); + getImpl()->resetCacheIDs(); +} + +const char * Config::getActiveView( int index ) const +{ + if( index<0 || + index >= static_cast(getImpl()->m_activeViews.size())) + { + return nullptr; + } + + return getImpl()->m_activeViews[index].c_str(); +} + +int Config::getNumActiveViews() const +{ + return static_cast(getImpl()->m_activeViews.size()); +} + int Config::getNumDisplaysAll() const noexcept { return static_cast(getImpl()->m_displays.size()); @@ -5601,6 +5785,8 @@ void Config::Impl::checkVersionConsistency(ConstTransformRcPtr & transform) cons void Config::Impl::checkVersionConsistency() const { + unsigned int hexVersion = (m_majorVersion << 24) | (m_minorVersion << 16); + // Check for the Transforms. ConstTransformVec transforms; @@ -5670,18 +5856,44 @@ void Config::Impl::checkVersionConsistency() const } } - // Check for the DisplayColorSpaces. + // Check ColorSpace properties. - if (m_majorVersion < 2) + const int nbCS = m_allColorSpaces->getNumColorSpaces(); + for (int i = 0; i < nbCS; ++i) { - const int nbCS = m_allColorSpaces->getNumColorSpaces(); - for (int i = 0; i < nbCS; ++i) + // Check for display color spaces. + + const auto & cs = m_allColorSpaces->getColorSpaceByIndex(i); + if (m_majorVersion < 2) { - const auto & cs = m_allColorSpaces->getColorSpaceByIndex(i); - if (MatchReferenceType(SEARCH_REFERENCE_SPACE_DISPLAY, cs->getReferenceSpaceType())) + if (MatchReferenceType(SEARCH_REFERENCE_SPACE_DISPLAY, cs->getReferenceSpaceType())) { throw Exception("Only version 2 (or higher) can have DisplayColorSpaces."); } + } + + // Check for new color space attributes. + + if (m_majorVersion < 2) + { + if (*cs->getInteropID()) + { + std::ostringstream os; + os << "Config failed validation. The color space '" << cs->getName() << "' "; + os << "has non-empty InteropID and config version is less than 2.0."; + throw Exception(os.str().c_str()); + } + } + + if (hexVersion < 0x02050000) + { + if (cs->getInterchangeAttributes().size()>0) + { + std::ostringstream os; + os << "Config failed validation. The color space '" << cs->getName() << "' "; + os << "has non-empty interchange attributes and config version is less than 2.5."; + throw Exception(os.str().c_str()); + } } } diff --git a/src/OpenColorIO/Context.cpp b/src/OpenColorIO/Context.cpp index 3a1294be58..b7e71ae6e7 100644 --- a/src/OpenColorIO/Context.cpp +++ b/src/OpenColorIO/Context.cpp @@ -253,7 +253,7 @@ void Context::setWorkingDir(const char * dirname) { AutoMutex lock(getImpl()->m_resultsCacheMutex); - getImpl()->m_workingDir = dirname; + getImpl()->m_workingDir = dirname ? dirname : ""; getImpl()->clearCaches(); } diff --git a/src/OpenColorIO/Look.cpp b/src/OpenColorIO/Look.cpp index c0e7cbef13..9830d0a460 100644 --- a/src/OpenColorIO/Look.cpp +++ b/src/OpenColorIO/Look.cpp @@ -86,7 +86,7 @@ const char * Look::getName() const void Look::setName(const char * name) { - getImpl()->m_name = name; + getImpl()->m_name = name ? name : ""; } const char * Look::getProcessSpace() const @@ -96,7 +96,7 @@ const char * Look::getProcessSpace() const void Look::setProcessSpace(const char * processSpace) { - getImpl()->m_processSpace = processSpace; + getImpl()->m_processSpace = processSpace ? processSpace : ""; } ConstTransformRcPtr Look::getTransform() const @@ -126,7 +126,7 @@ const char * Look::getDescription() const void Look::setDescription(const char * description) { - getImpl()->m_description = description; + getImpl()->m_description = description ? description : ""; } bool CollectContextVariables(const Config & config, diff --git a/src/OpenColorIO/OCIOYaml.cpp b/src/OpenColorIO/OCIOYaml.cpp index b1cee18982..c6920c3dea 100644 --- a/src/OpenColorIO/OCIOYaml.cpp +++ b/src/OpenColorIO/OCIOYaml.cpp @@ -33,6 +33,33 @@ namespace { typedef YAML::const_iterator Iterator; +std::string SanitizeNewlines(const std::string &input) +{ + if (input.empty()) + return input; + + // YAML is changing the trailing newlines when reading them: + // - Written as YAML::Literal (starts with a "|"), descriptions will be read back with a + // single newline. One is added if there was none, only one is kept if there were + // several. + // - Written as YAML::Value string (does not start with "|"), all trailing newlines ('\n') + // are preserved. + // Trailing newlines are inconsistently preserved, lets remove them in all cases. + std::string str = input; + auto last = str.back(); + while (last == '\n' && str.length()) + { + str.pop_back(); + last = str.back(); + } + + // Also, note that a \n is only interpreted as a newline if it is used in a string that is + // within double quotes. E.g., "A string \n with embedded \n newlines." Indeed, without the + // double quotes the backslash is generally not interpreted as an escape character in YAML. + + return str; +} + // Basic types inline void load(const YAML::Node& node, bool& x) @@ -185,25 +212,7 @@ inline void save(YAML::Emitter& out, Interpolation interp) inline void loadDescription(const YAML::Node& node, std::string& x) { load(node, x); - if (!x.empty()) - { - // YAML is changing the trailing newlines when reading them: - // - Written as YAML::Literal (starts with a "|"), descriptions will be read back with a - // single newline. One is added if there was none, only one is kept if there were - // several. - // - Written as YAML::Value string (does not start with "|"), all trailing newlines ('\n') - // are preserved. - // Trailing newlines are inconsistently preserved, lets remove them in all cases. - auto last = x.back(); - while (last == '\n' && x.length()) - { - x.pop_back(); - last = x.back(); - } - } - // Also, note that a \n is only interpreted as a newline if it is used in a string that is - // within double quotes. E.g., "A string \n with embedded \n newlines." Indeed, without the - // double quotes the backslash is generally not interpreted as an escape character in YAML. + x = SanitizeNewlines(x); } inline void saveDescription(YAML::Emitter & out, const char * desc) @@ -211,15 +220,7 @@ inline void saveDescription(YAML::Emitter & out, const char * desc) if (desc && *desc) { // Remove trailing newlines so that only one is saved because they won't be read back. - std::string descStr{ desc }; - { - auto last = descStr.back(); - while (last == '\n' && descStr.length()) - { - descStr.pop_back(); - last = descStr.back(); - } - } + std::string descStr = SanitizeNewlines(desc); out << YAML::Key << "description" << YAML::Value; if (descStr.find_first_of('\n') != std::string::npos) @@ -229,7 +230,6 @@ inline void saveDescription(YAML::Emitter & out, const char * desc) out << descStr; } } -// inline void LogUnknownKeyWarning(const YAML::Node & node, const YAML::Node & key) @@ -319,6 +319,31 @@ inline void CheckDuplicates(const YAML::Node & node) } } +// Custom Key Loader + +struct CustomKeysLoader +{ + using Type = std::vector>; + Type m_keyVals; +}; + +inline void loadCustomKeys(const YAML::Node& node, CustomKeysLoader & ck, const char* sectionName) +{ + if (node.Type() == YAML::NodeType::Map) + { + for (Iterator iter = node.begin(); iter != node.end(); ++iter) + { + ck.m_keyVals.push_back(std::make_pair(iter->first, iter->second)); + } + } + else + { + std::ostringstream ss; + ss << "Expected a YAML map in the " << sectionName << " section."; + throwError(node, ss.str().c_str()); + } +} + // View inline void load(const YAML::Node& node, View& v) @@ -3202,11 +3227,38 @@ inline void load(const YAML::Node& node, ColorSpaceRcPtr& cs, unsigned int major cs->addAlias(alias.c_str()); } } + else if (key == "interop_id") + { + load(iter->second, stringval); + cs->setInteropID(stringval.c_str()); + } else if(key == "description") { loadDescription(iter->second, stringval); cs->setDescription(stringval.c_str()); } + else if (key == "interchange") + { + CustomKeysLoader kv; + loadCustomKeys(iter->second, kv, "ColorSpace interchange"); + + for (const auto& keyval : kv.m_keyVals) + { + std::string keystr = keyval.first.as(); + std::string valstr = keyval.second.as(); + valstr = SanitizeNewlines(valstr); + + // OCIO exception means the key is not recognized. Convert that to a warning. + try + { + cs->setInterchangeAttribute(keystr.c_str(), valstr.c_str()); + } + catch (Exception &) + { + LogUnknownKeyWarning(key, keyval.first); + } + } + } else if(key == "family") { load(iter->second, stringval); @@ -3323,11 +3375,23 @@ inline void save(YAML::Emitter& out, ConstColorSpaceRcPtr cs, unsigned int major } out << YAML::Flow << YAML::Value << aliases; } + + const std::string interopID{ cs->getInteropID() }; + if (!interopID.empty()) + { + out << YAML::Key << "interop_id"; + out << YAML::Value << interopID; + } + out << YAML::Key << "family" << YAML::Value << cs->getFamily(); + out << YAML::Key << "equalitygroup" << YAML::Value << cs->getEqualityGroup(); + out << YAML::Key << "bitdepth" << YAML::Value; save(out, cs->getBitDepth()); + saveDescription(out, cs->getDescription()); + out << YAML::Key << "isdata" << YAML::Value << cs->isData(); if(cs->getNumCategories() > 0) @@ -3348,6 +3412,27 @@ inline void save(YAML::Emitter& out, ConstColorSpaceRcPtr cs, unsigned int major out << YAML::Value << is; } + auto interchangemap = cs->getInterchangeAttributes(); + if (interchangemap.size()) + { + out << YAML::Key << "interchange"; + out << YAML::Value; + out << YAML::BeginMap; + for (const auto& keyval : interchangemap) + { + std::string valStr = SanitizeNewlines(keyval.second); + + out << YAML::Key << keyval.first << YAML::Value; + if (valStr.find_first_of('\n') != std::string::npos) + { + out << YAML::Literal; + } + out << valStr; + } + + out << YAML::EndMap; + } + out << YAML::Key << "allocation" << YAML::Value; save(out, cs->getAllocation()); if(cs->getAllocationNumVars() > 0) @@ -3778,30 +3863,6 @@ inline void save(YAML::Emitter & out, ConstNamedTransformRcPtr & nt, unsigned in // File rules -struct CustomKeysLoader -{ - StringUtils::StringVec m_keyVals; -}; - -inline void loadCustomKeys(const YAML::Node& node, CustomKeysLoader & ck) -{ - if (node.Type() == YAML::NodeType::Map) - { - for (Iterator iter = node.begin(); iter != node.end(); ++iter) - { - const std::string & key = iter->first.as(); - const std::string & val = iter->second.as(); - - ck.m_keyVals.push_back(key); - ck.m_keyVals.push_back(val); - } - } - else - { - throwError(node, "The 'file_rules' custom attributes need to be a YAML map."); - } -} - inline void load(const YAML::Node & node, FileRulesRcPtr & fr, bool & defaultRuleFound) { if (node.Tag() != "Rule") @@ -3811,7 +3872,7 @@ inline void load(const YAML::Node & node, FileRulesRcPtr & fr, bool & defaultRul std::string stringval; std::string name, colorspace, pattern, extension, regex; - StringUtils::StringVec keyVals; + CustomKeysLoader::Type keyVals; for (Iterator iter = node.begin(); iter != node.end(); ++iter) { @@ -3847,7 +3908,7 @@ inline void load(const YAML::Node & node, FileRulesRcPtr & fr, bool & defaultRul else if (key == FileRuleUtils::CustomKey) { CustomKeysLoader kv; - loadCustomKeys(iter->second, kv); + loadCustomKeys(iter->second, kv, "file_rules custom attribute"); keyVals = kv.m_keyVals; } else @@ -3918,11 +3979,15 @@ inline void load(const YAML::Node & node, FileRulesRcPtr & fr, bool & defaultRul fr->insertRule(pos, name.c_str(), colorspace.c_str(), regex.c_str()); } } - const auto numKeyVal = keyVals.size() / 2; - for (size_t i = 0; i < numKeyVal; ++i) + + for (const auto& keyval : keyVals) { - fr->setCustomKey(pos, keyVals[i * 2].c_str(), keyVals[i * 2 + 1].c_str()); + fr->setCustomKey( + pos, + keyval.first.as().c_str(), + keyval.second.as().c_str()); } + } catch (Exception & ex) { @@ -3985,7 +4050,7 @@ inline void load(const YAML::Node & node, ViewingRulesRcPtr & vr) std::string stringval; std::string name; StringUtils::StringVec colorspaces, encodings; - StringUtils::StringVec keyVals; + CustomKeysLoader::Type keyVals; for (Iterator iter = node.begin(); iter != node.end(); ++iter) { @@ -4027,7 +4092,7 @@ inline void load(const YAML::Node & node, ViewingRulesRcPtr & vr) else if (key == ViewingRuleUtils::CustomKey) { CustomKeysLoader kv; - loadCustomKeys(iter->second, kv); + loadCustomKeys(iter->second, kv, "viewing_rules custom attribute"); keyVals = kv.m_keyVals; } else @@ -4050,10 +4115,12 @@ inline void load(const YAML::Node & node, ViewingRulesRcPtr & vr) vr->addEncoding(pos, is.c_str()); } - const auto numKeyVal = keyVals.size() / 2; - for (size_t i = 0; i < numKeyVal; ++i) + for (const auto& keyval : keyVals) { - vr->setCustomKey(pos, keyVals[i * 2].c_str(), keyVals[i * 2 + 1].c_str()); + vr->setCustomKey( + pos, + keyval.first.as().c_str(), + keyval.second.as().c_str()); } } catch (Exception & ex) @@ -4754,10 +4821,12 @@ inline void save(YAML::Emitter & out, const Config & config) { std::stringstream ss; const unsigned configMajorVersion = config.getMajorVersion(); + const unsigned configMinorVersion = config.getMinorVersion(); + ss << configMajorVersion; - if(config.getMinorVersion()!=0) + if(configMinorVersion != 0) { - ss << "." << config.getMinorVersion(); + ss << "." << configMinorVersion; } out << YAML::Block; @@ -4992,13 +5061,26 @@ inline void save(YAML::Emitter & out, const Config & config) out << YAML::Newline; out << YAML::Key << "active_displays"; StringUtils::StringVec active_displays; - if(config.getActiveDisplays() != NULL && strlen(config.getActiveDisplays()) > 0) - active_displays = SplitStringEnvStyle(config.getActiveDisplays()); + int nDisplays = config.getNumActiveDisplays(); + active_displays.reserve( nDisplays ); + for (int i = 0; i < nDisplays; i++) + { + active_displays.push_back(config.getActiveDisplay(i)); + } + + // The YAML library will wrap names that use a comma in quotes. out << YAML::Value << YAML::Flow << active_displays; + out << YAML::Key << "active_views"; StringUtils::StringVec active_views; - if(config.getActiveViews() != NULL && strlen(config.getActiveViews()) > 0) - active_views = SplitStringEnvStyle(config.getActiveViews()); + int nViews = config.getNumActiveViews(); + active_views.reserve( nViews ); + for (int i = 0; i < nViews; i++) + { + active_views.push_back(config.getActiveView(i)); + } + + // The YAML library will wrap names that use a comma in quotes. out << YAML::Value << YAML::Flow << active_views; const std::string inactiveCSs = config.getInactiveColorSpaces(); diff --git a/src/OpenColorIO/ParseUtils.cpp b/src/OpenColorIO/ParseUtils.cpp index c8b77c5f3a..6e3f056f39 100644 --- a/src/OpenColorIO/ParseUtils.cpp +++ b/src/OpenColorIO/ParseUtils.cpp @@ -687,33 +687,158 @@ bool StrEqualsCaseIgnore(const std::string & a, const std::string & b) return 0 == Platform::Strcasecmp(a.c_str(), b.c_str()); } +// Find the end of a name from a list contained in a string. +// The elements of the list are separated by the character defined by the sep argument. +// A name can be surrounded by quotes to enable names including the sep symbol, in +// other words, to prevent it from being used as a separator. +static int FindEndOfName(const std::string & s, size_t start, char sep) +{ + int currentPos = static_cast(start); + int nameEndPos = currentPos; + bool isEndFound = false; + + std::string symbols = "\""; + symbols += sep; + + while ( !isEndFound ) + { + nameEndPos = static_cast( s.find_first_of( symbols, currentPos ) ); + if ( nameEndPos == static_cast(std::string::npos) ) + { + // Reached the end of the list. + nameEndPos = static_cast( s.size() ); + isEndFound = true; + } + else if ( s[nameEndPos] == '"' ) + { + // Found a quote, need to find the next one. + nameEndPos = static_cast( s.find_first_of("\"", nameEndPos + 1) ); + if ( nameEndPos == (int)std::string::npos ) + { + // Reached the end of the list instead. + std::ostringstream os; + os << "The string '" << s << "' is not correctly formatted. " << + "It is missing a closing quote."; + throw Exception(os.str().c_str()); + } + else + { + // Found the second quote, need to continue the search for a symbol + // separating the elements (: or ,). + currentPos = nameEndPos + 1; + } + } + else if( s[nameEndPos] == sep ) + { + // Found a symbol separating the elements, stop here. + isEndFound = true; + } + } + + return nameEndPos; +} + StringUtils::StringVec SplitStringEnvStyle(const std::string & str) { - StringUtils::StringVec outputvec; const std::string s = StringUtils::Trim(str); - if (StringUtils::Find(s, ",") != std::string::npos) + if( s.size() == 0 ) { - outputvec = StringUtils::Split(s, ','); + // Look parsing always wants a result, even if an empty string. + return { "" }; } - else if (StringUtils::Find(s, ":") != std::string::npos) + + StringUtils::StringVec outputvec; + auto foundComma = s.find_first_of(","); + auto foundColon = s.find_first_of(":"); + + if( foundComma != std::string::npos || + foundColon != std::string::npos ) { - outputvec = StringUtils::Split(s, ':'); + int currentPos = 0; + while( s.size() > 0 && + currentPos <= (int)s.size() ) + { + int nameEndPos = FindEndOfName( s, + currentPos, + foundComma != std::string::npos ? ',' : ':' ); + if(nameEndPos > currentPos) + { + outputvec.push_back(s.substr(currentPos, nameEndPos - currentPos)); + currentPos = nameEndPos + 1; + } + else + { + outputvec.push_back(""); + currentPos += 1; + } + } } else { + // If there is no comma or colon, consider the string as a single element. outputvec.push_back(s); } - for (auto & val : outputvec) + for ( auto & val : outputvec ) { - val = StringUtils::Trim(val); + const std::string trimmedValue = StringUtils::Trim(val); + + // If the trimmed value is surrounded by quotes, remove them. + if( trimmedValue.size() > 1 && + trimmedValue[0] == '"' && + trimmedValue[trimmedValue.size() - 1] == '"' ) + { + val = trimmedValue.substr(1, trimmedValue.size() - 2); + } + else + { + val = trimmedValue; + } } + return outputvec; } std::string JoinStringEnvStyle(const StringUtils::StringVec & outputvec) { - return StringUtils::Join(outputvec, ','); + std::string result; + const int nElement = static_cast(outputvec.size()); + if( nElement == 0 ) + { + return ""; + } + + // Check if the value contains a symbol that could be interpreted as a separator + // and if it is not already surrounded by quotes. + const std::string& firstValue = outputvec[0]; + if( firstValue.find_first_of(",:") != std::string::npos && + firstValue.size() > 1 && + firstValue[0] != '"' && + firstValue[firstValue.size() - 1] != '"' ) + { + result += "\"" + firstValue + "\""; + } + else + { + result += firstValue; + } + + for( int i = 1; i < nElement; ++i ) + { + if( outputvec[i].find_first_of(",:") != std::string::npos && + outputvec[i].size() > 1 && + outputvec[i][0] != '"' && + outputvec[i][outputvec[i].size() - 1] != '"' ) + { + result += ", \"" + outputvec[i] + "\""; + } + else + { + result += ", " + outputvec[i]; + } + } + + return result; } // Return a vector of strings that are both in vec1 and vec2. diff --git a/src/OpenColorIO/Platform.cpp b/src/OpenColorIO/Platform.cpp index 1dbd846f35..0e7fc68b2d 100644 --- a/src/OpenColorIO/Platform.cpp +++ b/src/OpenColorIO/Platform.cpp @@ -154,6 +154,11 @@ bool isEnvPresent(const char * name) int Strcasecmp(const char * str1, const char * str2) { + if (!str1 || !str2) + { + throw Exception("String pointer for comparison must not be null."); + } + #ifdef _WIN32 return ::_stricmp(str1, str2); #else @@ -163,6 +168,11 @@ int Strcasecmp(const char * str1, const char * str2) int Strncasecmp(const char * str1, const char * str2, size_t n) { + if (!str1 || !str2) + { + throw Exception("String pointer for comparison must not be null."); + } + #ifdef _WIN32 return ::_strnicmp(str1, str2, n); #else diff --git a/src/OpenColorIO/SystemMonitor_macos.cpp b/src/OpenColorIO/SystemMonitor_macos.cpp index 9957903885..4b212e6850 100644 --- a/src/OpenColorIO/SystemMonitor_macos.cpp +++ b/src/OpenColorIO/SystemMonitor_macos.cpp @@ -190,13 +190,13 @@ void SystemMonitorsImpl::getAllMonitors() CFDictionaryGetKeysAndValues(productInfo, nullptr, (const void **)&values[0]); const CFIndex bufferSize = CFStringGetLength(values[0]) + 1; // +1 for null termination - char buffer[bufferSize]; + std::vector buffer(bufferSize); // Return false if the buffer is too small or if the conversion fails. - if (CFStringGetCString(values[0], buffer, bufferSize, kCFStringEncodingUTF8)) + if (CFStringGetCString(values[0], buffer.data(), buffer.size(), kCFStringEncodingUTF8)) { // Build a name using the vendor information. - displayName = buffer; + displayName = buffer.data(); // Add the display unit number (i.e. identify the display's framebuffer) // to differentiate same type of monitors. diff --git a/src/OpenColorIO/ViewTransform.cpp b/src/OpenColorIO/ViewTransform.cpp index 0a7ca21091..7fd8914a34 100644 --- a/src/OpenColorIO/ViewTransform.cpp +++ b/src/OpenColorIO/ViewTransform.cpp @@ -91,7 +91,7 @@ const char * ViewTransform::getName() const noexcept void ViewTransform::setName(const char * name) noexcept { - getImpl()->m_name = name; + getImpl()->m_name = name ? name : ""; } const char * ViewTransform::getFamily() const noexcept @@ -101,7 +101,7 @@ const char * ViewTransform::getFamily() const noexcept void ViewTransform::setFamily(const char * family) { - getImpl()->m_family = family; + getImpl()->m_family = family ? family : ""; } const char * ViewTransform::getDescription() const noexcept @@ -111,7 +111,7 @@ const char * ViewTransform::getDescription() const noexcept void ViewTransform::setDescription(const char * description) { - getImpl()->m_description = description; + getImpl()->m_description = description ? description : ""; } bool ViewTransform::hasCategory(const char * category) const diff --git a/src/OpenColorIO/ViewingRules.cpp b/src/OpenColorIO/ViewingRules.cpp index be780c94e1..bbe27172c1 100644 --- a/src/OpenColorIO/ViewingRules.cpp +++ b/src/OpenColorIO/ViewingRules.cpp @@ -38,8 +38,6 @@ class ViewingRule { public: - using CustomKeys = std::map; - ViewingRule() = delete; ViewingRule(const ViewingRule &) = delete; ViewingRule & operator=(const ViewingRule &) = delete; diff --git a/src/apps/ociocheck/main.cpp b/src/apps/ociocheck/main.cpp index b3c8c33854..5c66a7eba0 100644 --- a/src/apps/ociocheck/main.cpp +++ b/src/apps/ociocheck/main.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include namespace OCIO = OCIO_NAMESPACE; @@ -26,6 +27,83 @@ const char * DESC_STRING = "\n\n" "Ociocheck can also be used to clean up formatting on an existing profile\n" "that has been manually edited, using the '-o' option.\n"; + +// returns true if the interopID is valid +bool isValidInteropID(const std::string& id) +{ + // See https://github.com/AcademySoftwareFoundation/ColorInterop for the details. + + static const std::set cifTextureIDs = { + "lin_ap1_scene", + "lin_ap0_scene", + "lin_rec709_scene", + "lin_p3d65_scene", + "lin_rec2020_scene", + "lin_adobergb_scene", + "lin_ciexyzd65_scene", + "srgb_rec709_scene", + "g22_rec709_scene", + "g18_rec709_scene", + "srgb_ap1_scene", + "g22_ap1_scene", + "srgb_p3d65_scene", + "g22_adobergb_scene", + "data", + "unknown" + }; + + static const std::set cifDisplayIDs = { + "srgb_rec709_display", + "g24_rec709_display", + "srgb_p3d65_display", + "srgbx_p3d65_display", + "pq_p3d65_display", + "pq_rec2020_display", + "hlg_rec2020_display", + "g22_rec709_display", + "g22_adobergb_display", + "g26_p3d65_display", + "g26_xyzd65_display", + "pq_xyzd65_display", + }; + + if (id.empty()) + return true; + + // Check if has a namespace. + size_t pos = id.find(':'); + if (pos == std::string::npos) + { + // No namespace, so id must be in the Color Interop Forum ID list. + if (cifTextureIDs.count(id) == 0 && cifDisplayIDs.count(id)==0) + { + std::cout << "ERROR: InteropID '" << id << "' is not valid. " + "It should either be one of Color Interop Forum standard IDs or " + "it must contain a namespace followed by ':', e.g. 'mycompany:mycolorspace'." << + std::endl; + return false; + } + } + else + { + // Namespace found, split into namespace and id. + std::string ns = id.substr(0, pos); + std::string cs = id.substr(pos+1); + + // Id should not be in the Color Interop Forum ID list. + if (cifTextureIDs.count(cs) > 0 || cifDisplayIDs.count(cs)> 0) + { + std::cout << "ERROR: InteropID '" << id << "' is not valid. " + "The ID part must not be one of the Color Interop Forum standard IDs when a namespace is used." << + std::endl; + return false; + } + } + + // all clear. + return true; +} + int main(int argc, const char **argv) { bool help = false; @@ -298,6 +376,15 @@ int main(int argc, const char **argv) OCIO::COLORSPACE_ALL, i)); + std::string interopID = cs->getInteropID(); + if (!interopID.empty()) + { + if (!isValidInteropID(interopID)) + { + errorcount += 1; + } + } + // Try to load the transform for the to_ref direction -- this will load any LUTs. bool toRefOK = true; std::string toRefErrorText; diff --git a/src/apps/ocioconvert/main.cpp b/src/apps/ocioconvert/main.cpp index e8071c76fa..31a5ed3542 100644 --- a/src/apps/ocioconvert/main.cpp +++ b/src/apps/ocioconvert/main.cpp @@ -658,6 +658,14 @@ int main(int argc, const char **argv) if (outputcolorspace) { imgOutput->attribute("oiio:ColorSpace", outputcolorspace); + + // Set the color space interopID if available. + auto cs = config->getColorSpace(outputcolorspace); + const char* interopID = cs ? cs->getInteropID() : nullptr; + if(interopID && *interopID) + { + imgOutput->attribute("colorInteropID", interopID); + } } imgOutput->write(outputimage, userOutputBitDepth); diff --git a/src/apps/ocioview/ocioview/config_cache.py b/src/apps/ocioview/ocioview/config_cache.py index 280442ca97..eb25d3d35c 100644 --- a/src/apps/ocioview/ocioview/config_cache.py +++ b/src/apps/ocioview/ocioview/config_cache.py @@ -117,15 +117,7 @@ def get_active_displays(cls) -> list[str]: :return: List of active displays from the current config """ if not cls.validate() or cls._active_displays is None: - cls._active_displays = list( - filter( - None, - re.split( - r" *[,:] *", - ocio.GetCurrentConfig().getActiveDisplays(), - ), - ) - ) + cls._active_displays = list(ocio.GetCurrentConfig().getActiveDisplays()) return cls._active_displays @@ -135,14 +127,7 @@ def get_active_views(cls) -> list[str]: :return: List of active views from the current config """ if not cls.validate() or cls._active_views is None: - cls._active_views = list( - filter( - None, - re.split( - r" *[,:] *", ocio.GetCurrentConfig().getActiveViews() - ), - ) - ) + cls._active_views = list(ocio.GetCurrentConfig().getActiveViews()) return cls._active_views diff --git a/src/bindings/python/PyColorSpace.cpp b/src/bindings/python/PyColorSpace.cpp index 6827943bc0..7007ac8b36 100644 --- a/src/bindings/python/PyColorSpace.cpp +++ b/src/bindings/python/PyColorSpace.cpp @@ -61,7 +61,7 @@ void bindPyColorSpace(py::module & m) py::class_( clsColorSpace, "ColorSpaceCategoryIterator"); - auto clsColorSpacAliasIterator = + auto clsColorSpaceAliasIterator = py::class_( clsColorSpace, "ColorSpaceAliasIterator"); @@ -90,7 +90,9 @@ void bindPyColorSpace(py::module & m) const std::vector & allocationVars, const TransformRcPtr & toReference, const TransformRcPtr & fromReference, - const std::vector & categories) + const std::vector & categories, + const std::string & interopID + ) { ColorSpaceRcPtr p = ColorSpace::Create(referenceSpace); if (!aliases.empty()) @@ -102,11 +104,12 @@ void bindPyColorSpace(py::module & m) } } // Setting the name will remove alias named the same, so set name after. - if (!name.empty()) { p->setName(name.c_str()); } - if (!family.empty()) { p->setFamily(family.c_str()); } - if (!encoding.empty()) { p->setEncoding(encoding.c_str()); } - if (!equalityGroup.empty()) { p->setEqualityGroup(equalityGroup.c_str()); } - if (!description.empty()) { p->setDescription(description.c_str()); } + if (!name.empty()) { p->setName(name.c_str()); } + if (!family.empty()) { p->setFamily(family.c_str()); } + if (!encoding.empty()) { p->setEncoding(encoding.c_str()); } + if (!equalityGroup.empty()) { p->setEqualityGroup(equalityGroup.c_str()); } + if (!description.empty()) { p->setDescription(description.c_str()); } + if (!interopID.empty()) { p->setInteropID(interopID.c_str()); } p->setBitDepth(bitDepth); p->setIsData(isData); p->setAllocation(allocation); @@ -150,6 +153,7 @@ void bindPyColorSpace(py::module & m) "toReference"_a = DEFAULT->getTransform(COLORSPACE_DIR_TO_REFERENCE), "fromReference"_a = DEFAULT->getTransform(COLORSPACE_DIR_FROM_REFERENCE), "categories"_a = getCategoriesStdVec(DEFAULT), + "interopID"_a = DEFAULT->getInteropID(), DOC(ColorSpace, Create, 2)) .def("__deepcopy__", [](const ConstColorSpaceRcPtr & self, py::dict) @@ -193,6 +197,16 @@ void bindPyColorSpace(py::module & m) DOC(ColorSpace, getDescription)) .def("setDescription", &ColorSpace::setDescription, "description"_a, DOC(ColorSpace, setDescription)) + .def("getInteropID", &ColorSpace::getInteropID, + DOC(ColorSpace, getInteropID)) + .def("setInteropID", &ColorSpace::setInteropID, "interopID"_a, + DOC(ColorSpace, setInteropID)) + .def("getInterchangeAttribute", &ColorSpace::getInterchangeAttribute, "attrName"_a, + DOC(ColorSpace, getInterchangeAttribute)) + .def("setInterchangeAttribute", &ColorSpace::setInterchangeAttribute, "attrName"_a, "attrValue"_a, + DOC(ColorSpace, setInterchangeAttribute)) + .def("getInterchangeAttributes", &ColorSpace:: getInterchangeAttributes, + DOC(ColorSpace, getInterchangeAttributes)) .def("getBitDepth", &ColorSpace::getBitDepth, DOC(ColorSpace, getBitDepth)) .def("setBitDepth", &ColorSpace::setBitDepth, "bitDepth"_a, @@ -268,7 +282,7 @@ void bindPyColorSpace(py::module & m) return it.m_obj->getCategory(i); }); - clsColorSpacAliasIterator + clsColorSpaceAliasIterator .def("__len__", [](ColorSpaceAliasIterator & it) { return it.m_obj->getNumAliases(); diff --git a/src/bindings/python/PyConfig.cpp b/src/bindings/python/PyConfig.cpp index 58f3f52fca..6e7971844f 100644 --- a/src/bindings/python/PyConfig.cpp +++ b/src/bindings/python/PyConfig.cpp @@ -26,6 +26,8 @@ enum ConfigIterator IT_SHARED_VIEW, IT_DISPLAY_VIEW, IT_DISPLAY_VIEW_COLORSPACE, + IT_ACTIVE_DISPLAYS_LIST, + IT_ACTIVE_VIEWS_LIST, IT_LOOK_NAME, IT_LOOK, IT_VIEW_TRANSFORM_NAME, @@ -63,6 +65,8 @@ using ViewForColorSpaceIterator = PyIterator; using ViewForViewTypeIterator = PyIterator; +using ActiveDisplaysListIterator = PyIterator; +using ActiveViewsListIterator = PyIterator; using LookNameIterator = PyIterator; using LookIterator = PyIterator; using ViewTransformNameIterator = PyIterator; @@ -142,6 +146,14 @@ void bindPyConfig(py::module & m) py::class_( clsConfig, "ViewForViewTypeIterator"); + auto clsActiveDisplaysListIterator = + py::class_( + clsConfig, "ActiveDisplaysListIterator"); + + auto clsActiveViewsListIterator = + py::class_( + clsConfig, "ActiveViewsListIterator"); + auto clsLookNameIterator = py::class_( clsConfig, "LookNameIterator"); @@ -576,12 +588,33 @@ void bindPyConfig(py::module & m) // Active Displays and Views .def("setActiveDisplays", &Config::setActiveDisplays, "displays"_a, DOC(Config, setActiveDisplays)) - .def("getActiveDisplays", &Config::getActiveDisplays, - DOC(Config, getActiveDisplays)) + .def("getActiveDisplays", [](ConfigRcPtr & self) + { + return ActiveDisplaysListIterator(self); + }) + .def("addActiveDisplay", &Config::addActiveDisplay, "display"_a, + DOC(Config, addActiveDisplay)) + .def("removeActiveDisplay", &Config::removeActiveDisplay, "display"_a, + DOC(Config, removeActiveDisplay)) + .def("clearActiveDisplays", &Config::clearActiveDisplays, + DOC(Config, clearActiveDisplays)) + .def("getNumActiveDisplays", &Config::getNumActiveDisplays, + DOC(Config, getNumActiveDisplays)) + .def("setActiveViews", &Config::setActiveViews, "views"_a, DOC(Config, setActiveViews)) - .def("getActiveViews", &Config::getActiveViews, - DOC(Config, getActiveViews)) + .def("getActiveViews", [](ConfigRcPtr & self) + { + return ActiveViewsListIterator(self); + }) + .def("addActiveView", &Config::addActiveView, "view"_a, + DOC(Config, addActiveView)) + .def("removeActiveView", &Config::removeActiveView, "view"_a, + DOC(Config, removeActiveView)) + .def("clearActiveViews", &Config::clearActiveViews, + DOC(Config, clearActiveViews)) + .def("getNumActiveViews", &Config::getNumActiveViews, + DOC(Config, getNumActiveViews)) // Luma .def("getDefaultLumaCoefs", [](ConfigRcPtr & self) @@ -1261,6 +1294,34 @@ void bindPyConfig(py::module & m) std::get<1>(it.m_args).c_str(), i); }); + clsActiveDisplaysListIterator + .def("__len__", [](ActiveDisplaysListIterator & it) { return it.m_obj->getNumActiveDisplays(); }) + .def("__getitem__", [](ActiveDisplaysListIterator & it, int i) + { + it.checkIndex(i, it.m_obj->getNumActiveDisplays()); + return it.m_obj->getActiveDisplay(i); + }) + .def("__iter__", [](ActiveDisplaysListIterator & it) -> ActiveDisplaysListIterator & { return it; }) + .def("__next__", [](ActiveDisplaysListIterator & it) + { + int i = it.nextIndex(it.m_obj->getNumActiveDisplays()); + return it.m_obj->getActiveDisplay(i); + }); + + clsActiveViewsListIterator + .def("__len__", [](ActiveViewsListIterator & it) { return it.m_obj->getNumActiveViews(); }) + .def("__getitem__", [](ActiveViewsListIterator & it, int i) + { + it.checkIndex(i, it.m_obj->getNumActiveViews()); + return it.m_obj->getActiveView(i); + }) + .def("__iter__", [](ActiveViewsListIterator & it) -> ActiveViewsListIterator & { return it; }) + .def("__next__", [](ActiveViewsListIterator & it) + { + int i = it.nextIndex(it.m_obj->getNumActiveViews()); + return it.m_obj->getActiveView(i); + }); + clsLookNameIterator .def("__len__", [](LookNameIterator & it) { return it.m_obj->getNumLooks(); }) .def("__getitem__", [](LookNameIterator & it, int i) diff --git a/src/libutils/imageioapphelpers/imageio.cpp b/src/libutils/imageioapphelpers/imageio.cpp index b1701f8909..cda93b3e3a 100644 --- a/src/libutils/imageioapphelpers/imageio.cpp +++ b/src/libutils/imageioapphelpers/imageio.cpp @@ -192,6 +192,9 @@ ptrdiff_t ImageIO::getImageBytes() const void ImageIO::init(const ImageIO & img, BitDepth bitDepth) { m_impl->init(*img.m_impl, bitDepth); + + // Do not propagate colorInteropID. + attribute("colorInteropID", "unknown"); } void ImageIO::init(long width, long height, ChannelOrdering chanOrder, BitDepth bitDepth) diff --git a/tests/cpu/CMakeLists.txt b/tests/cpu/CMakeLists.txt index 66c32cb8a7..322f0f94bf 100755 --- a/tests/cpu/CMakeLists.txt +++ b/tests/cpu/CMakeLists.txt @@ -35,7 +35,7 @@ function(add_ocio_test NAME SOURCES PRIVATE_INCLUDES) sampleicc::sampleicc unittest_data utils::strings - ${YAML_CPP_LIBRARIES} + yaml-cpp::yaml-cpp testutils MINIZIP::minizip-ng xxHash diff --git a/tests/cpu/CPUProcessor_tests.cpp b/tests/cpu/CPUProcessor_tests.cpp index c2fed39022..925a75397c 100644 --- a/tests/cpu/CPUProcessor_tests.cpp +++ b/tests/cpu/CPUProcessor_tests.cpp @@ -57,8 +57,9 @@ OCIO_ADD_TEST(CPUProcessor, flag_composition) // a missing/partial optimization. -template -OCIO::ConstCPUProcessorRcPtr ComputeValues(OCIO::ConstProcessorRcPtr processor, +template +OCIO::ConstCPUProcessorRcPtr ComputeValues(unsigned line, + OCIO::ConstProcessorRcPtr processor, const void * inImg, OCIO::ChannelOrdering inChans, const void * resImg, @@ -147,12 +148,12 @@ OCIO_ADD_TEST(CPUProcessor, with_one_matrix) 1.5025f, 0.9050f, 1.5896f, 1.5000f, 2.4002f, 1.6505f, 2.0707f, 0.5000f }; - ComputeValues(processor, - &f_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, - NB_PIXELS, - 1e-5f); + ComputeValues( + __LINE__, processor, + &f_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, + NB_PIXELS, + 1e-5f); } { @@ -161,12 +162,12 @@ OCIO_ADD_TEST(CPUProcessor, with_one_matrix) 0.182999f, 0.9050f, 2.9091f, 1.5000f, 1.0807f, 1.6505f, 3.3902f, 0.5000f }; - ComputeValues(processor, - &f_inImg[0], OCIO::CHANNEL_ORDERING_BGRA, - &resImg[0], OCIO::CHANNEL_ORDERING_BGRA, - NB_PIXELS, - 1e-5f); + ComputeValues( + __LINE__, processor, + &f_inImg[0], OCIO::CHANNEL_ORDERING_BGRA, + &resImg[0], OCIO::CHANNEL_ORDERING_BGRA, + NB_PIXELS, + 1e-5f); } { @@ -175,12 +176,12 @@ OCIO_ADD_TEST(CPUProcessor, with_one_matrix) 0.602300f, 0.585199f, 1.909399f, 2.400200f, 1.500000f, 1.330700f, 2.390500f, 1.400200f }; - ComputeValues(processor, - &f_inImg[0], OCIO::CHANNEL_ORDERING_ABGR, - &resImg[0], OCIO::CHANNEL_ORDERING_ABGR, - NB_PIXELS, - 1e-5f); + ComputeValues( + __LINE__, processor, + &f_inImg[0], OCIO::CHANNEL_ORDERING_ABGR, + &resImg[0], OCIO::CHANNEL_ORDERING_ABGR, + NB_PIXELS, + 1e-5f); } { @@ -189,12 +190,12 @@ OCIO_ADD_TEST(CPUProcessor, with_one_matrix) 1.5896f, 0.9050f, 1.5025f, 1.5000f, 2.0707f, 1.6505f, 2.4002f, 0.5000f }; - ComputeValues(processor, - &f_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &resImg[0], OCIO::CHANNEL_ORDERING_BGRA, - NB_PIXELS, - 1e-5f); + ComputeValues( + __LINE__, processor, + &f_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &resImg[0], OCIO::CHANNEL_ORDERING_BGRA, + NB_PIXELS, + 1e-5f); } { @@ -203,12 +204,12 @@ OCIO_ADD_TEST(CPUProcessor, with_one_matrix) 1.5000f, 1.5896f, 0.9050f, 1.5025f, 0.5000f, 2.0707f, 1.6505f, 2.4002f }; - ComputeValues(processor, - &f_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &resImg[0], OCIO::CHANNEL_ORDERING_ABGR, - NB_PIXELS, - 1e-5f); + ComputeValues( + __LINE__, processor, + &f_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &resImg[0], OCIO::CHANNEL_ORDERING_ABGR, + NB_PIXELS, + 1e-5f); } { @@ -222,12 +223,12 @@ OCIO_ADD_TEST(CPUProcessor, with_one_matrix) 1.5025f, 0.9050f, 1.5896f, 2.4002f, 1.6505f, 2.0707f }; - ComputeValues(processor, - &inImg[0], OCIO::CHANNEL_ORDERING_RGB, - &resImg[0], OCIO::CHANNEL_ORDERING_RGB, - NB_PIXELS, - 1e-5f); + ComputeValues( + __LINE__, processor, + &inImg[0], OCIO::CHANNEL_ORDERING_RGB, + &resImg[0], OCIO::CHANNEL_ORDERING_RGB, + NB_PIXELS, + 1e-5f); } { @@ -241,12 +242,12 @@ OCIO_ADD_TEST(CPUProcessor, with_one_matrix) 0.182999f, 0.905000f, 2.909100f, 1.080700f, 1.650500f, 3.390200f }; - ComputeValues(processor, - &inImg[0], OCIO::CHANNEL_ORDERING_BGR, - &resImg[0], OCIO::CHANNEL_ORDERING_BGR, - NB_PIXELS, - 1e-5f); + ComputeValues( + __LINE__, processor, + &inImg[0], OCIO::CHANNEL_ORDERING_BGR, + &resImg[0], OCIO::CHANNEL_ORDERING_BGR, + NB_PIXELS, + 1e-5f); } { @@ -260,12 +261,12 @@ OCIO_ADD_TEST(CPUProcessor, with_one_matrix) 1.58960f, 0.9050f, 1.5025f, 2.070699f, 1.6505f, 2.4002f }; - ComputeValues(processor, - &inImg[0], OCIO::CHANNEL_ORDERING_RGB, - &resImg[0], OCIO::CHANNEL_ORDERING_BGR, - NB_PIXELS, - 1e-5f); + ComputeValues( + __LINE__, processor, + &inImg[0], OCIO::CHANNEL_ORDERING_RGB, + &resImg[0], OCIO::CHANNEL_ORDERING_BGR, + NB_PIXELS, + 1e-5f); } { @@ -279,12 +280,12 @@ OCIO_ADD_TEST(CPUProcessor, with_one_matrix) 1.58960f, 0.9050f, 1.5025f, 0.5f, 2.070699f, 1.6505f, 2.4002f, 0.5f }; - ComputeValues(processor, - &inImg[0], OCIO::CHANNEL_ORDERING_RGB, - &resImg[0], OCIO::CHANNEL_ORDERING_BGRA, - NB_PIXELS, - 1e-5f); + ComputeValues( + __LINE__, processor, + &inImg[0], OCIO::CHANNEL_ORDERING_RGB, + &resImg[0], OCIO::CHANNEL_ORDERING_BGRA, + NB_PIXELS, + 1e-5f); } { @@ -298,12 +299,12 @@ OCIO_ADD_TEST(CPUProcessor, with_one_matrix) 1.58960f, 0.9050f, 1.5025f, 2.070699f, 1.6505f, 2.4002f }; - ComputeValues(processor, - &inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &resImg[0], OCIO::CHANNEL_ORDERING_BGR, - NB_PIXELS, - 1e-5f); + ComputeValues( + __LINE__, processor, + &inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &resImg[0], OCIO::CHANNEL_ORDERING_BGR, + NB_PIXELS, + 1e-5f); } const std::vector ui16_inImg = @@ -317,12 +318,12 @@ OCIO_ADD_TEST(CPUProcessor, with_one_matrix) 1.40117657f, 0.40245315f, 0.08460631f, 0.5f, 1.47832620f, 0.70781672f, 1.08070004f, 0.5f }; - ComputeValues(processor, - &ui16_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, - NB_PIXELS, - 1e-5f); + ComputeValues( + __LINE__, processor, + &ui16_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, + NB_PIXELS, + 1e-5f); } { @@ -331,11 +332,11 @@ OCIO_ADD_TEST(CPUProcessor, with_one_matrix) 65535, 26375, 5545, 32768, 65535, 46387, 65535, 32768 }; - ComputeValues(processor, - &ui16_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &ui16_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, + NB_PIXELS); } { @@ -344,11 +345,11 @@ OCIO_ADD_TEST(CPUProcessor, with_one_matrix) 5545, 26375, 65535, 32768, 65535, 46387, 65535, 32768 }; - ComputeValues(processor, - &ui16_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &resImg[0], OCIO::CHANNEL_ORDERING_BGRA, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &ui16_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &resImg[0], OCIO::CHANNEL_ORDERING_BGRA, + NB_PIXELS); } { @@ -357,11 +358,11 @@ OCIO_ADD_TEST(CPUProcessor, with_one_matrix) 5545, 26375, 65535, 65535, 46387, 65535 }; - ComputeValues(processor, - &ui16_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &resImg[0], OCIO::CHANNEL_ORDERING_BGR, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &ui16_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &resImg[0], OCIO::CHANNEL_ORDERING_BGR, + NB_PIXELS); } { @@ -370,11 +371,11 @@ OCIO_ADD_TEST(CPUProcessor, with_one_matrix) 255, 103, 22, 128, 255, 180, 255, 128 }; - ComputeValues(processor, - &ui16_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &ui16_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, + NB_PIXELS); } { @@ -383,11 +384,11 @@ OCIO_ADD_TEST(CPUProcessor, with_one_matrix) 22, 103, 255, 255, 180, 255 }; - ComputeValues(processor, - &ui16_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &resImg[0], OCIO::CHANNEL_ORDERING_BGR, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &ui16_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &resImg[0], OCIO::CHANNEL_ORDERING_BGR, + NB_PIXELS); } { @@ -396,11 +397,11 @@ OCIO_ADD_TEST(CPUProcessor, with_one_matrix) 128, 22, 103, 255, 128, 255, 180, 255 }; - ComputeValues(processor, - &ui16_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &resImg[0], OCIO::CHANNEL_ORDERING_ABGR, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &ui16_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &resImg[0], OCIO::CHANNEL_ORDERING_ABGR, + NB_PIXELS); } // Test OCIO::BIT_DEPTH_UINT10. @@ -411,11 +412,11 @@ OCIO_ADD_TEST(CPUProcessor, with_one_matrix) 1023, 412, 87, 512, 1023, 724, 1023, 512 }; - ComputeValues(processor, - &ui16_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &ui10_resImg[0], OCIO::CHANNEL_ORDERING_RGBA, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &ui16_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &ui10_resImg[0], OCIO::CHANNEL_ORDERING_RGBA, + NB_PIXELS); const std::vector ui10_inImg = { 0, 8, 12, 256, @@ -427,11 +428,11 @@ OCIO_ADD_TEST(CPUProcessor, with_one_matrix) 65535, 27272, 9389, 65535, 65535, 28297, 11439, 65535 }; - ComputeValues(processor, - &ui10_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &ui16_resImg[0], OCIO::CHANNEL_ORDERING_RGBA, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &ui10_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &ui16_resImg[0], OCIO::CHANNEL_ORDERING_RGBA, + NB_PIXELS); } // Test OCIO::BIT_DEPTH_UINT12. @@ -442,11 +443,11 @@ OCIO_ADD_TEST(CPUProcessor, with_one_matrix) 4095, 1648, 346, 2048, 4095, 2899, 4095, 2048 }; - ComputeValues(processor, - &ui16_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &ui12_resImg[0], OCIO::CHANNEL_ORDERING_RGBA, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &ui16_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &ui12_resImg[0], OCIO::CHANNEL_ORDERING_RGBA, + NB_PIXELS); const std::vector ui12_inImg = { 0, 8, 12, 1024, @@ -458,11 +459,11 @@ OCIO_ADD_TEST(CPUProcessor, with_one_matrix) 65535, 26503, 6313, 65535, 65535, 26759, 6825, 65535 }; - ComputeValues(processor, - &ui12_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &ui16_resImg[0], OCIO::CHANNEL_ORDERING_RGBA, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &ui12_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &ui16_resImg[0], OCIO::CHANNEL_ORDERING_RGBA, + NB_PIXELS); } } @@ -499,12 +500,12 @@ OCIO_ADD_TEST(CPUProcessor, with_one_1d_lut) 0.29089212f, 0.50935059f, 1.91091322f, 1, 64, 64, 64, 0 }; - ComputeValues(processor, - &f_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, - NB_PIXELS, - 1e-7f); + ComputeValues( + __LINE__, processor, + &f_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, + NB_PIXELS, + 1e-7f); } { @@ -514,11 +515,11 @@ OCIO_ADD_TEST(CPUProcessor, with_one_1d_lut) 19064, 33380, 65535, 65535, 65535, 65535, 65535, 0 }; - ComputeValues(processor, - &f_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &f_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, + NB_PIXELS); } const std::vector ui16_inImg = @@ -534,11 +535,11 @@ OCIO_ADD_TEST(CPUProcessor, with_one_1d_lut) 0.00601041f, 0.00912247f, 0.01456576f, 0.00097657f, 0.03030112f, 0.13105739f, 64, 0.00781261f }; - ComputeValues(processor, - &ui16_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, - NB_PIXELS, 1e-7f); + ComputeValues( + __LINE__, processor, + &ui16_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, + NB_PIXELS, 1e-7f); } { @@ -548,11 +549,11 @@ OCIO_ADD_TEST(CPUProcessor, with_one_1d_lut) 394, 598, 955, 64, 1986, 8589, 65535, 512 }; - ComputeValues(processor, - &ui16_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &ui16_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, + NB_PIXELS); } { @@ -562,11 +563,11 @@ OCIO_ADD_TEST(CPUProcessor, with_one_1d_lut) 955, 598, 394, 64, 65535, 8589, 1986, 512 }; - ComputeValues(processor, - &ui16_inImg[0], OCIO::CHANNEL_ORDERING_BGRA, - &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &ui16_inImg[0], OCIO::CHANNEL_ORDERING_BGRA, + &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, + NB_PIXELS); } { @@ -576,11 +577,11 @@ OCIO_ADD_TEST(CPUProcessor, with_one_1d_lut) 955, 598, 394, 64, 65535, 8589, 1986, 512 }; - ComputeValues(processor, - &ui16_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &resImg[0], OCIO::CHANNEL_ORDERING_BGRA, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &ui16_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &resImg[0], OCIO::CHANNEL_ORDERING_BGRA, + NB_PIXELS); } { @@ -590,11 +591,11 @@ OCIO_ADD_TEST(CPUProcessor, with_one_1d_lut) 955, 598, 394, 65535, 8589, 1986 }; - ComputeValues(processor, - &ui16_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &resImg[0], OCIO::CHANNEL_ORDERING_BGR, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &ui16_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &resImg[0], OCIO::CHANNEL_ORDERING_BGR, + NB_PIXELS); } { @@ -604,11 +605,11 @@ OCIO_ADD_TEST(CPUProcessor, with_one_1d_lut) 394, 598, 955, 64, 1986, 8589, 65535, 512 }; - ComputeValues(processor, - &ui16_inImg[0], OCIO::CHANNEL_ORDERING_BGRA, - &resImg[0], OCIO::CHANNEL_ORDERING_BGRA, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &ui16_inImg[0], OCIO::CHANNEL_ORDERING_BGRA, + &resImg[0], OCIO::CHANNEL_ORDERING_BGRA, + NB_PIXELS); } { @@ -624,11 +625,11 @@ OCIO_ADD_TEST(CPUProcessor, with_one_1d_lut) 955, 598, 394, 0, 65535, 8589, 1986, 0 }; - ComputeValues(processor, - &my_i_inImg[0], OCIO::CHANNEL_ORDERING_RGB, - &resImg[0], OCIO::CHANNEL_ORDERING_BGRA, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &my_i_inImg[0], OCIO::CHANNEL_ORDERING_RGB, + &resImg[0], OCIO::CHANNEL_ORDERING_BGRA, + NB_PIXELS); } // Test OCIO::BIT_DEPTH_UINT10. @@ -640,11 +641,11 @@ OCIO_ADD_TEST(CPUProcessor, with_one_1d_lut) 6, 9, 15, 1, 31, 134, 1023, 8 }; - ComputeValues(processor, - &ui16_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &ui10_resImg[0], OCIO::CHANNEL_ORDERING_RGBA, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &ui16_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &ui10_resImg[0], OCIO::CHANNEL_ORDERING_RGBA, + NB_PIXELS); } { @@ -660,11 +661,11 @@ OCIO_ADD_TEST(CPUProcessor, with_one_1d_lut) 36, 106, 252, 0, 48, 1023, 384, 0 }; - ComputeValues(processor, - &ui10_inImg[0], OCIO::CHANNEL_ORDERING_RGB, - &ui10_resImg[0], OCIO::CHANNEL_ORDERING_RGBA, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &ui10_inImg[0], OCIO::CHANNEL_ORDERING_RGB, + &ui10_resImg[0], OCIO::CHANNEL_ORDERING_RGBA, + NB_PIXELS); const std::vector ui16_resImg = { 0, 394, 955, 0, @@ -672,11 +673,11 @@ OCIO_ADD_TEST(CPUProcessor, with_one_1d_lut) 2301, 6794, 16162, 0, 3092, 65535, 24593, 0 }; - ComputeValues(processor, - &ui10_inImg[0], OCIO::CHANNEL_ORDERING_RGB, - &ui16_resImg[0], OCIO::CHANNEL_ORDERING_RGBA, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &ui10_inImg[0], OCIO::CHANNEL_ORDERING_RGB, + &ui16_resImg[0], OCIO::CHANNEL_ORDERING_RGBA, + NB_PIXELS); } // Test OCIO::BIT_DEPTH_UINT12. @@ -688,11 +689,11 @@ OCIO_ADD_TEST(CPUProcessor, with_one_1d_lut) 25, 37, 60, 4, 124, 537, 4095, 32 }; - ComputeValues(processor, - &ui16_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &ui12_resImg[0], OCIO::CHANNEL_ORDERING_RGBA, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &ui16_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &ui12_resImg[0], OCIO::CHANNEL_ORDERING_RGBA, + NB_PIXELS); } { @@ -708,22 +709,22 @@ OCIO_ADD_TEST(CPUProcessor, with_one_1d_lut) 49, 103, 193, 0, 424, 1009, 4095, 0 }; - ComputeValues(processor, - &ui12_inImg[0], OCIO::CHANNEL_ORDERING_RGB, - &ui12_resImg[0], OCIO::CHANNEL_ORDERING_RGBA, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &ui12_inImg[0], OCIO::CHANNEL_ORDERING_RGB, + &ui12_resImg[0], OCIO::CHANNEL_ORDERING_RGBA, + NB_PIXELS); const std::vector ui16_resImg = { 0, 178, 394, 0, 598, 955, 1655, 0, 779, 1655, 3089, 0, 6789, 16143, 65535, 0 }; - ComputeValues(processor, - &ui12_inImg[0], OCIO::CHANNEL_ORDERING_RGB, - &ui16_resImg[0], OCIO::CHANNEL_ORDERING_RGBA, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &ui12_inImg[0], OCIO::CHANNEL_ORDERING_RGB, + &ui16_resImg[0], OCIO::CHANNEL_ORDERING_RGBA, + NB_PIXELS); } } @@ -794,11 +795,11 @@ OCIO_ADD_TEST(CPUProcessor, with_several_ops) 0.15488569f, 1.69210147f, 1.90666747f, 1.0f, 0.81575858f, 64.0f, 64.0f, 0.0f }; - ComputeValues(processor, - &f_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, - NB_PIXELS, 1e-7f); + ComputeValues( + __LINE__, processor, + &f_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, + NB_PIXELS, 1e-7f); } { @@ -808,11 +809,11 @@ OCIO_ADD_TEST(CPUProcessor, with_several_ops) 10150, 65535, 65535, 65535, 53461, 65535, 65535, 0 }; - ComputeValues(processor, - &f_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &f_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, + NB_PIXELS); } const std::vector i_inImg = @@ -828,12 +829,11 @@ OCIO_ADD_TEST(CPUProcessor, with_several_ops) 0.0f, 0.08474064f, 0.01450117f, 0.0f, 0.0f, 0.24826171f, 56.39490891f, 1.0f }; - auto cpuProcessor = ComputeValues(processor, - &i_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, - NB_PIXELS, 1e-7f); + auto cpuProcessor = ComputeValues( + __LINE__, processor, + &i_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, + NB_PIXELS, 1e-7f); // SSE2/AVX/AVX2 generate a slightly different LUT1D // floating error below the absErrorThreshold, but cacheID hash will be different @@ -874,11 +874,11 @@ OCIO_ADD_TEST(CPUProcessor, with_several_ops) 0, 5553, 950, 0, 0, 16270, 65535, 65535 }; - ComputeValues(processor, - &i_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &i_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, + NB_PIXELS); } { @@ -888,11 +888,11 @@ OCIO_ADD_TEST(CPUProcessor, with_several_ops) 0, 5553, 388, 0, 53461, 16270, 1982, 65535 }; - ComputeValues(processor, - &i_inImg[0], OCIO::CHANNEL_ORDERING_BGRA, - &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &i_inImg[0], OCIO::CHANNEL_ORDERING_BGRA, + &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, + NB_PIXELS); } { @@ -902,11 +902,11 @@ OCIO_ADD_TEST(CPUProcessor, with_several_ops) 950, 5553, 0, 0, 65535, 16270, 0, 65535 }; - ComputeValues(processor, - &i_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &resImg[0], OCIO::CHANNEL_ORDERING_BGRA, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &i_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &resImg[0], OCIO::CHANNEL_ORDERING_BGRA, + NB_PIXELS); } { @@ -916,11 +916,11 @@ OCIO_ADD_TEST(CPUProcessor, with_several_ops) 388, 5553, 0, 0, 1982, 16270, 53461, 65535 }; - ComputeValues(processor, - &i_inImg[0], OCIO::CHANNEL_ORDERING_BGRA, - &resImg[0], OCIO::CHANNEL_ORDERING_BGRA, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &i_inImg[0], OCIO::CHANNEL_ORDERING_BGRA, + &resImg[0], OCIO::CHANNEL_ORDERING_BGRA, + NB_PIXELS); } } @@ -960,11 +960,11 @@ OCIO_ADD_TEST(CPUProcessor, with_several_ops) 0.10089212f, 0.69935059f, 1.91072320f, 1.0f, 63.81000137f, 64.19000244f, 63.99980927f, 0.0f }; - ComputeValues(processor, - &f_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, - NB_PIXELS, 1e-7f); + ComputeValues( + __LINE__, processor, + &f_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, + NB_PIXELS, 1e-7f); } { @@ -974,11 +974,11 @@ OCIO_ADD_TEST(CPUProcessor, with_several_ops) 6612, 45832, 65535, 65535, 65535, 65535, 65535, 0 }; - ComputeValues(processor, - &f_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &f_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, + NB_PIXELS); } const std::vector i_inImg = @@ -993,11 +993,11 @@ OCIO_ADD_TEST(CPUProcessor, with_several_ops) -0.18398958f, 0.19912247f, 0.01437576f, 0.0f, -0.15969887f, 0.32105737f, 63.99980927f, 0.0f }; - ComputeValues(processor, - &i_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, - NB_PIXELS, 1e-7f); + ComputeValues( + __LINE__, processor, + &i_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, + NB_PIXELS, 1e-7f); } { @@ -1007,11 +1007,11 @@ OCIO_ADD_TEST(CPUProcessor, with_several_ops) 381, 13049, 0, 0, 1973, 21040, 65535, 0 }; - ComputeValues(processor, - &i_inImg[0], OCIO::CHANNEL_ORDERING_BGRA, - &resImg[0], OCIO::CHANNEL_ORDERING_BGRA, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &i_inImg[0], OCIO::CHANNEL_ORDERING_BGRA, + &resImg[0], OCIO::CHANNEL_ORDERING_BGRA, + NB_PIXELS); } } @@ -1053,11 +1053,11 @@ OCIO_ADD_TEST(CPUProcessor, with_several_ops) -0.23451784f, 0.92250210f, 3.26448941f, 1.0f, 3.43709063f, 3.43709063f, 3.43709063f, 0.0f }; - ComputeValues(processor, - &f_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, - &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, - NB_PIXELS, 1e-7f); + ComputeValues( + __LINE__, processor, + &f_inImg[0], OCIO::CHANNEL_ORDERING_RGBA, + &resImg[0], OCIO::CHANNEL_ORDERING_RGBA, + NB_PIXELS, 1e-7f); } const std::vector i_inImg = @@ -1073,11 +1073,11 @@ OCIO_ADD_TEST(CPUProcessor, with_several_ops) 0, 0, 0, 0, 0, 12526, 65535, 0 }; - ComputeValues(processor, - &i_inImg[0], OCIO::CHANNEL_ORDERING_BGRA, - &resImg[0], OCIO::CHANNEL_ORDERING_BGRA, - NB_PIXELS); + ComputeValues( + __LINE__, processor, + &i_inImg[0], OCIO::CHANNEL_ORDERING_BGRA, + &resImg[0], OCIO::CHANNEL_ORDERING_BGRA, + NB_PIXELS); } } } diff --git a/tests/cpu/ColorSpace_tests.cpp b/tests/cpu/ColorSpace_tests.cpp index 54521be171..1e47ef8111 100644 --- a/tests/cpu/ColorSpace_tests.cpp +++ b/tests/cpu/ColorSpace_tests.cpp @@ -10,6 +10,7 @@ #include "testutils/UnitTest.h" #include "UnitTestUtils.h" +#include "UnitTestLogUtils.h" namespace OCIO = OCIO_NAMESPACE; @@ -39,7 +40,39 @@ OCIO_ADD_TEST(ColorSpace, basic) OCIO_CHECK_ASSERT(!cs->isData()); OCIO_CHECK_EQUAL(OCIO::ALLOCATION_UNIFORM, cs->getAllocation()); OCIO_CHECK_EQUAL(0, cs->getAllocationNumVars()); - + + // Check the nullptr assignment hardening. + // First set the fields to non-empty values. + cs->setName("NAME"); + cs->setDescription("DESC"); + cs->setFamily("FAMILY"); + cs->setEqualityGroup("EQGRP"); + cs->setEncoding("ENC"); + cs->setInteropID("interop"); + OCIO_CHECK_NO_THROW(cs->setInterchangeAttribute("amf_transform_ids", "AMF")); + OCIO_CHECK_NO_THROW(cs->setInterchangeAttribute("icc_profile_name", "ICC")); + + // Set to nullptr, this should erase the old values. + OCIO_CHECK_NO_THROW(cs->setName(nullptr)); + OCIO_CHECK_NO_THROW(cs->setDescription(nullptr)); + OCIO_CHECK_NO_THROW(cs->setFamily(nullptr)); + OCIO_CHECK_NO_THROW(cs->setEqualityGroup(nullptr)); + OCIO_CHECK_NO_THROW(cs->setEncoding(nullptr)); + OCIO_CHECK_NO_THROW(cs->setInteropID(nullptr)); + OCIO_CHECK_NO_THROW(cs->setInterchangeAttribute("amf_transform_ids", nullptr)); + OCIO_CHECK_NO_THROW(cs->setInterchangeAttribute("icc_profile_name", nullptr)); + + // Check that the values are empty now. + OCIO_CHECK_ASSERT(!*cs->getName()); + OCIO_CHECK_ASSERT(!*cs->getDescription()); + OCIO_CHECK_ASSERT(!*cs->getFamily()); + OCIO_CHECK_ASSERT(!*cs->getEqualityGroup()); + OCIO_CHECK_ASSERT(!*cs->getEncoding()); + OCIO_CHECK_ASSERT(!*cs->getInteropID()); + OCIO_CHECK_ASSERT(!*cs->getInterchangeAttribute("amf_transform_ids")); + OCIO_CHECK_ASSERT(!*cs->getInterchangeAttribute("icc_profile_name")); + + // Test set/get roundtrip. cs->setName("name"); OCIO_CHECK_EQUAL(std::string("name"), cs->getName()); cs->setFamily("family"); @@ -63,10 +96,17 @@ OCIO_ADD_TEST(ColorSpace, basic) cs->getAllocationVars(readVars); OCIO_CHECK_EQUAL(1.f, readVars[0]); OCIO_CHECK_EQUAL(2.f, readVars[1]); + cs->setInteropID("interop_id"); + OCIO_CHECK_EQUAL(std::string("interop_id"), cs->getInteropID()); + cs->setInterchangeAttribute("amf_transform_ids", "amf_transform_id1\namf_transform_id2"); + OCIO_CHECK_EQUAL(std::string("amf_transform_id1\namf_transform_id2"), + cs->getInterchangeAttribute("amf_transform_ids")); + cs->setInterchangeAttribute("icc_profile_name","icc_profile_name"); + OCIO_CHECK_EQUAL(std::string("icc_profile_name"), cs->getInterchangeAttribute("icc_profile_name")); std::ostringstream oss; oss << *cs; - OCIO_CHECK_EQUAL(oss.str().size(), 193); + OCIO_CHECK_EQUAL(oss.str().size(), 306); } OCIO_ADD_TEST(ColorSpace, alias) @@ -256,7 +296,7 @@ active_views: [] OCIO_CHECK_EQUAL(cfgString, os.str()); } - // Adding a color space that uses all parameters. + // Adding a color space that uses all parameters (as of 2.0). { constexpr char End[]{ R"(colorspaces: - ! @@ -619,6 +659,207 @@ active_views: [] OCIO_CHECK_EQUAL(cfgRes, os.str()); } + + // Test that the interop_id is valid in v2.0 config too. + { + constexpr char End[]{R"(colorspaces: + - ! + name: raw + aliases: [ data ] + interop_id: data + family: raw + equalitygroup: "" + bitdepth: 32f + description: Some text. + isdata: true + allocation: uniform +)"}; + std::string cfgString{Start}; + cfgString += End; + + std::istringstream is; + is.str(cfgString); + OCIO::ConstConfigRcPtr config; + OCIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is)); + auto cs = config->getColorSpace("raw"); + OCIO_CHECK_ASSERT(cs); + OCIO_CHECK_EQUAL(std::string(cs->getInteropID()), "data"); + + OCIO_CHECK_NO_THROW(config->validate()); + } + + // Test that the undefined interop_id does not pass validation + { + constexpr char End[]{R"(colorspaces: + - ! + name: raw + interop_id: data + family: raw + equalitygroup: "" + bitdepth: 32f + description: Some text. + isdata: true + allocation: uniform +)"}; + std::string cfgString{Start}; + cfgString += End; + + std::istringstream is; + is.str(cfgString); + OCIO::ConstConfigRcPtr config; + OCIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is)); + auto cs = config->getColorSpace("raw"); + OCIO_CHECK_ASSERT(cs); + OCIO_CHECK_EQUAL(std::string(cs->getInteropID()), "data"); + + OCIO_CHECK_THROW_WHAT(config->validate(), + OCIO::Exception, + "Config failed color space validation. The color space 'raw' refers " + "to an interop ID, 'data', which is not defined in this config."); + } + + // Test that the interop id can be found in another color space. + { + constexpr char End[]{R"(colorspaces: + - ! + name: raw + interop_id: data + family: raw + equalitygroup: "" + bitdepth: 32f + description: one data color space. + isdata: true + allocation: uniform + - ! + name: data + interop_id: data + family: raw + equalitygroup: "" + bitdepth: 32f + description: another data color space. + isdata: true + allocation: uniform +)"}; + std::string cfgString{Start}; + cfgString += End; + + std::istringstream is; + is.str(cfgString); + OCIO::ConstConfigRcPtr config; + OCIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is)); + + OCIO_CHECK_NO_THROW(config->validate()); + } + + // Test that the interchange is NOT valid in v2.0 config. + { + constexpr char End[]{R"(colorspaces: + - ! + name: raw + interchange: + amf_transform_ids: should NOT be valid in 2.0 config + family: raw + equalitygroup: "" + bitdepth: 32f + description: Some text. + isdata: true + allocation: uniform +)"}; + std::string cfgString{Start}; + cfgString += End; + + std::istringstream is; + is.str(cfgString); + OCIO::ConstConfigRcPtr config; + OCIO_CHECK_THROW_WHAT( + config = OCIO::Config::CreateFromStream(is), + OCIO::Exception, + "Config failed validation. The color space 'raw' has non-empty " + "interchange attributes and config version is less than 2.5."); + } + + // Interchange tests in 2.5 + constexpr char Start2_5[]{R"(ocio_profile_version: 2.5 + +environment: + {} +search_path: "" +strictparsing: false +luma: [0.2126, 0.7152, 0.0722] + +roles: + default: raw + +file_rules: + - ! {name: ColorSpaceNamePathSearch} + - ! {name: Default, colorspace: default} + +displays: + sRGB: + - ! {name: Raw, colorspace: raw} + +active_displays: [] +active_views: [] + +)"}; + + // Test that the interchange is valid in v2.5 config. + { + constexpr char End_amf[]{R"( +colorspaces: + - ! + name: raw + interchange: + amf_transform_ids: This is valid in 2.5 config + family: raw + equalitygroup: "" + bitdepth: 32f + description: Some text. + isdata: true + allocation: uniform +)"}; + + std::string cfgString{Start2_5}; + cfgString += End_amf; + + std::istringstream is; + is.str(cfgString); + OCIO::ConstConfigRcPtr config; + OCIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is)); + OCIO_REQUIRE_EQUAL(!config, false); + auto attrMap = config->getColorSpace("raw")->getInterchangeAttributes(); + OCIO_CHECK_EQUAL(attrMap.size(), 1); + } + + // Test that the unknown interchange attrib will be ignored in 2.5. + { + constexpr char End_unkown[]{R"( +colorspaces: + - ! + name: raw + interchange: + my-attrib: will be ignored + family: raw + equalitygroup: "" + bitdepth: 32f + description: Some text. + isdata: true + allocation: uniform +)"}; + + std::string cfgString{Start2_5}; + cfgString += End_unkown; + + std::istringstream is; + is.str(cfgString); + OCIO::ConstConfigRcPtr config; + OCIO::LogGuard logGuard; + OCIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is)); + OCIO_CHECK_EQUAL(logGuard.output(), "[OpenColorIO Warning]: Unknown key in interchange: 'my-attrib'.\n"); + OCIO_REQUIRE_EQUAL(!config, false); + auto attrMap = config->getColorSpace("raw")->getInterchangeAttributes(); + OCIO_CHECK_EQUAL(attrMap.size(), 0); + } } OCIO_ADD_TEST(Config, use_alias) @@ -734,6 +975,320 @@ active_views: [] "colorspace"); } +OCIO_ADD_TEST(ColorSpace, interop_id) +{ + OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create(); + + // Test default value. + OCIO_CHECK_EQUAL(std::string(cs->getInteropID()), ""); + + // Test setting and getting single profile name. + const char * interop_id = "srgb_p3d65_scene"; + cs->setInteropID(interop_id); + OCIO_CHECK_EQUAL(std::string(cs->getInteropID()), interop_id); + + // Test setting empty string. + cs->setInteropID(""); + OCIO_CHECK_EQUAL(std::string(cs->getInteropID()), ""); + + // Test setting and getting another value. + const char * anotherID= "lin_rec2020_scene"; + cs->setInteropID(anotherID); + OCIO_CHECK_EQUAL(std::string(cs->getInteropID()), anotherID); + + // Test setting null pointer (should be safe). + cs->setInteropID("something"); + OCIO_CHECK_NO_THROW(cs->setInteropID(nullptr)); + OCIO_CHECK_EQUAL(std::string(cs->getInteropID()), ""); + + // Test copy constructor preserves InteropID. + cs->setInteropID(interop_id); + OCIO::ColorSpaceRcPtr copy = cs->createEditableCopy(); + OCIO_CHECK_EQUAL(std::string(copy->getInteropID()), interop_id); + + // Test valid InteropID with colon in the middle. + const char * validColonMiddle = "namespace:colorspace_name"; + OCIO_CHECK_NO_THROW(cs->setInteropID(validColonMiddle)); + OCIO_CHECK_EQUAL(std::string(cs->getInteropID()), validColonMiddle); + + // Test invalid InteropID with multiple colons. + OCIO_CHECK_THROW_WHAT(cs->setInteropID("name:space:cs_name"), OCIO::Exception, + "Only one ':' is allowed to separate the namespace and the color space."); + + // Test invalid InteropID with colon at the end. + OCIO_CHECK_THROW_WHAT(cs->setInteropID("namespace:"), OCIO::Exception, + " If ':' is used, both the namespace and the color space parts must be non-empty."); + + // Test invalid InteropID with empty namespace or color space. + OCIO_CHECK_THROW_WHAT(cs->setInteropID(":cs_name"), OCIO::Exception, + "If ':' is used, both the namespace and the color space parts must be non-empty."); + + // Test invalid InteropID with illegal characters. + OCIO_CHECK_THROW_WHAT(cs->setInteropID("café_scene"), OCIO::Exception, + "Only lowercase a-z, 0-9 and . - _ ~ / * # % ^ + ( ) [ ] | are allowed."); + + OCIO_CHECK_THROW_WHAT(cs->setInteropID("UPPERCASE"), OCIO::Exception, + "Only lowercase a-z, 0-9 and . - _ ~ / * # % ^ + ( ) [ ] | are allowed."); + + OCIO_CHECK_THROW_WHAT(cs->setInteropID("{curly_bracket}"), OCIO::Exception, + "Only lowercase a-z, 0-9 and . - _ ~ / * # % ^ + ( ) [ ] | are allowed."); + + OCIO_CHECK_THROW_WHAT(cs->setInteropID("\\backslash"), OCIO::Exception, + "Only lowercase a-z, 0-9 and . - _ ~ / * # % ^ + ( ) [ ] | are allowed."); + + OCIO_CHECK_THROW_WHAT(cs->setInteropID(" space "), OCIO::Exception, + "Only lowercase a-z, 0-9 and . - _ ~ / * # % ^ + ( ) [ ] | are allowed."); +} + +OCIO_ADD_TEST(ColorSpace, interop_id_serialization) +{ + // Test YAML serialization and deserialization of InteropID. + auto cfg = OCIO::Config::Create(); + auto cs = OCIO::ColorSpace::Create(); + cs->setName("test_colorspace"); + + const std::string interop_id = "lin_rec709_scene"; + + cs->setInteropID(interop_id.c_str()); + cfg->addColorSpace(cs); + + // Serialize the Config. + std::stringstream ss; + cfg->serialize(ss); + std::string yamlStr = ss.str(); + + // Verify interop_id appears in YAML. + OCIO_CHECK_NE(yamlStr.find("interop_id"), std::string::npos); + OCIO_CHECK_NE(yamlStr.find(interop_id), std::string::npos); + + // Deserialize and verify. + std::istringstream iss(yamlStr); + OCIO::ConstConfigRcPtr deserializedCfg; + OCIO_CHECK_NO_THROW(deserializedCfg = OCIO::Config::CreateFromStream(iss)); + + // Verify interop_id is preserved. + OCIO::ConstColorSpaceRcPtr deserializedCs = deserializedCfg->getColorSpace("test_colorspace"); + OCIO_CHECK_EQUAL(std::string(deserializedCs->getInteropID()), interop_id); + + // verify that that versions earlier than 2.0 reject interop_id. + OCIO::ConfigRcPtr cfgCopy = cfg->createEditableCopy(); + cfgCopy->setVersion(2, 0); + OCIO_CHECK_NO_THROW(cfgCopy->serialize(ss)); + + cfgCopy->setVersion(1, 0); + OCIO_CHECK_THROW_WHAT( + cfgCopy->serialize(ss), + OCIO::Exception, + "Config failed validation. The color space 'test_colorspace' has non-empty " + "InteropID and config version is less than 2.0."); + + // Test with empty interop_id (should not appear in YAML). + cs->setInteropID(nullptr); + cfg->addColorSpace(cs); // Replace the existing CS. + ss.str(""); + cfg->serialize(ss); + std::string yamlStr2 = ss.str(); + + // Verify empty interop_id does not appear in YAML. + OCIO_CHECK_EQUAL(yamlStr2.find("interop_id"), std::string::npos); +} + +OCIO_ADD_TEST(ColorSpace, amf_transform_ids) +{ + OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create(); + + // Test default value. + OCIO_CHECK_EQUAL(std::string(cs->getInterchangeAttribute("amf_transform_ids")), ""); + + // Test setting and getting single ID. + const char * singleID = "urn:ampas:aces:transformId:v1.5:ACEScsc.Academy.ACEScc_to_ACES.a1.0.3"; + cs->setInterchangeAttribute("amf_transform_ids", singleID); + OCIO_CHECK_EQUAL(std::string(cs->getInterchangeAttribute("amf_transform_ids")), singleID); + + // Test setting to empty string. + cs->setInterchangeAttribute("amf_transform_ids", ""); + OCIO_CHECK_EQUAL(std::string(cs->getInterchangeAttribute("amf_transform_ids")), ""); + + // Test setting and getting multiple IDs. + const char * multipleIDs = + "urn:ampas:aces:transformId:v1.5:ACEScsc.Academy.ACEScc_to_ACES.a1.0.3\n" + "urn:ampas:aces:transformId:v1.5:ACEScsc.Academy.ACES_to_ACEScc.a1.0.3"; + cs->setInterchangeAttribute("amf_transform_ids", multipleIDs); + OCIO_CHECK_EQUAL(std::string(cs->getInterchangeAttribute("amf_transform_ids")), multipleIDs); + + // Test setting to null pointer (should be safe). + cs->setInterchangeAttribute("amf_transform_ids", "something"); + cs->setInterchangeAttribute("amf_transform_ids", nullptr); + OCIO_CHECK_EQUAL(std::string(cs->getInterchangeAttribute("amf_transform_ids")), ""); + + // Test copy constructor preserves AMF transform IDs. + cs->setInterchangeAttribute("amf_transform_ids", singleID); + OCIO::ColorSpaceRcPtr copy = cs->createEditableCopy(); + OCIO_CHECK_EQUAL(std::string(copy->getInterchangeAttribute("amf_transform_ids")), singleID); +} + +OCIO_ADD_TEST(ColorSpace, amf_transform_ids_serialization) +{ + // Test YAML serialization and deserialization of AmfTransformIDs. + auto cfg = OCIO::Config::Create(); + auto cs = OCIO::ColorSpace::Create(); + cs->setName("test_colorspace"); + + const std::string amfIDs = + "urn:ampas:aces:transformId:v1.5:ACEScsc.Academy.ACEScc_to_ACES.a1.0.3\n" + "urn:ampas:aces:transformId:v1.5:ACEScsc.Academy.ACES_to_ACEScc.a1.0.3"; + + cs->setInterchangeAttribute("amf_transform_ids", amfIDs.c_str()); + cfg->addColorSpace(cs); + + // Serialize the Config. + std::stringstream ss; + cfg->serialize(ss); + std::string yamlStr = ss.str(); + + // Verify AmfTransformIDs appears in YAML. + OCIO_CHECK_NE(yamlStr.find("amf_transform_ids"), std::string::npos); + OCIO_CHECK_NE(yamlStr.find("ACEScsc.Academy.ACEScc_to_ACES"), std::string::npos); + + // Deserialize and verify. + std::istringstream iss(yamlStr); + OCIO::ConstConfigRcPtr deserializedCfg; + OCIO_CHECK_NO_THROW(deserializedCfg = OCIO::Config::CreateFromStream(iss)); + + // Verify AmfTransformIDs is preserved. + OCIO::ConstColorSpaceRcPtr deserializedCs = deserializedCfg->getColorSpace("test_colorspace"); + auto deserializedIDs = deserializedCs->getInterchangeAttribute("amf_transform_ids"); + OCIO_CHECK_EQUAL(deserializedIDs, amfIDs); + + // Verify that that earlier versions reject amf_transform_ids. + OCIO::ConfigRcPtr cfgCopy = cfg->createEditableCopy(); + cfgCopy->setVersion(2,4); + OCIO_CHECK_THROW_WHAT(cfgCopy->serialize(ss), + OCIO::Exception, + "has non-empty interchange attributes and config version is less than 2.5."); + + // Test with empty AmfTransformIDs (should not appear in YAML). + cs->setInterchangeAttribute("amf_transform_ids", nullptr); + cfg->addColorSpace(cs); // Replace the existing CS. + ss.str(""); + cfg->serialize(ss); + std::string yamlStr2 = ss.str(); + + // Verify empty AmfTransformIDs does not appear in YAML. + OCIO_CHECK_EQUAL(yamlStr2.find("amf_transform_ids"), std::string::npos); +} + +OCIO_ADD_TEST(ColorSpace, icc_profile_name) +{ + OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create(); + + // Test default value. + OCIO_CHECK_EQUAL(std::string(cs->getInterchangeAttribute("icc_profile_name")), ""); + + // Test setting and getting single profile name. + const char * profileName = "sRGB IEC61966-2.1"; + cs->setInterchangeAttribute("icc_profile_name",profileName); + OCIO_CHECK_EQUAL(std::string(cs->getInterchangeAttribute("icc_profile_name")), profileName); + + // Test setting and getting another profile name. + const char * anotherProfile = "Adobe RGB (1998)"; + cs->setInterchangeAttribute("icc_profile_name", anotherProfile); + OCIO_CHECK_EQUAL(std::string(cs->getInterchangeAttribute("icc_profile_name")), anotherProfile); + + // Test setting empty string. + cs->setInterchangeAttribute("icc_profile_name", ""); + OCIO_CHECK_EQUAL(std::string(cs->getInterchangeAttribute("icc_profile_name")), ""); + + // Test setting null pointer (should be safe). + cs->setInterchangeAttribute("icc_profile_name", "something"); + OCIO_CHECK_NO_THROW(cs->setInterchangeAttribute("icc_profile_name", nullptr)); + OCIO_CHECK_EQUAL(std::string(cs->getInterchangeAttribute("icc_profile_name")), ""); + + // Test copy constructor preserves ICC profile name. + cs->setInterchangeAttribute("icc_profile_name", profileName); + OCIO::ColorSpaceRcPtr copy = cs->createEditableCopy(); + OCIO_CHECK_EQUAL(std::string(copy->getInterchangeAttribute("icc_profile_name")), profileName); +} + +OCIO_ADD_TEST(ColorSpace, icc_profile_name_serialization) +{ + // Test YAML serialization and deserialization of IccProfileName. + auto cfg = OCIO::Config::Create(); + auto cs = OCIO::ColorSpace::Create(); + cs->setName("test_colorspace"); + + const std::string profileName = "sRGB IEC61966-2.1"; + + cs->setInterchangeAttribute("icc_profile_name", profileName.c_str()); + cfg->addColorSpace(cs); + + // Serialize the Config. + std::stringstream ss; + cfg->serialize(ss); + std::string yamlStr = ss.str(); + + // Verify IccProfileName appears in YAML. + OCIO_CHECK_NE(yamlStr.find("icc_profile_name"), std::string::npos); + OCIO_CHECK_NE(yamlStr.find(profileName), std::string::npos); + + // Deserialize and verify. + std::istringstream iss(yamlStr); + OCIO::ConstConfigRcPtr deserializedCfg; + OCIO_CHECK_NO_THROW(deserializedCfg = OCIO::Config::CreateFromStream(iss)); + + // Verify IccProfileName is preserved. + OCIO::ConstColorSpaceRcPtr deserializedCs = deserializedCfg->getColorSpace("test_colorspace"); + OCIO_CHECK_EQUAL(std::string(deserializedCs->getInterchangeAttribute("icc_profile_name")), profileName); + + // verify that that earlier versions reject icc_profile_name. + OCIO::ConfigRcPtr cfgCopy = cfg->createEditableCopy(); + cfgCopy->setVersion(2, 4); + OCIO_CHECK_THROW_WHAT(cfgCopy->serialize(ss), + OCIO::Exception, + "has non-empty interchange attributes and config version is less than 2.5."); + + // Test with empty IccProfileName (should not appear in YAML, and so won't invalidate a 2.4 config). + cs->setInterchangeAttribute("icc_profile_name", nullptr); + cfg->addColorSpace(cs); // replace the existing CS + ss.str(""); + cfg->serialize(ss); + std::string yamlStr2 = ss.str(); + + // Verify empty IccProfileName does not appear in YAML. + OCIO_CHECK_EQUAL(yamlStr2.find("icc_profile_name"), std::string::npos); +} + +OCIO_ADD_TEST(ColorSpace, unknown_interchange_attrib) +{ + OCIO::ColorSpaceRcPtr cs = OCIO::ColorSpace::Create(); + + // Getting should throw. + OCIO_CHECK_THROW_WHAT(std::string(cs->getInterchangeAttribute("unknown_attrib")), + OCIO::Exception, + "Unknown attribute name"); + + // Empty name is not legal. + OCIO_CHECK_THROW_WHAT(std::string(cs->getInterchangeAttribute("")), OCIO::Exception, + "Unknown attribute name"); + OCIO_CHECK_THROW_WHAT(std::string(cs->getInterchangeAttribute(nullptr)), OCIO::Exception, + "Unknown attribute name"); + + // Setting should throw too. + OCIO_CHECK_THROW_WHAT(cs->setInterchangeAttribute("unknown_attribute1", "unknown"), + OCIO::Exception, + "Unknown attribute name"); + OCIO_CHECK_THROW_WHAT(cs->setInterchangeAttribute("unknown_attribute2", ""), + OCIO::Exception, + "Unknown attribute name"); + OCIO_CHECK_THROW_WHAT(cs->setInterchangeAttribute("unknown_attribute3", nullptr), + OCIO::Exception, + "Unknown attribute name"); + + // Make sure none of the above was stored. + auto attrMap = cs->getInterchangeAttributes(); + OCIO_CHECK_EQUAL(attrMap.size(), 0); +} + OCIO_ADD_TEST(Config, is_colorspace_linear) { @@ -1702,3 +2257,4 @@ inactive_colorspaces: [scene-linear Rec.709-sRGB, ACES2065-1] ); } } + diff --git a/tests/cpu/Config_tests.cpp b/tests/cpu/Config_tests.cpp index b2a272f0ef..06ac3d743c 100644 --- a/tests/cpu/Config_tests.cpp +++ b/tests/cpu/Config_tests.cpp @@ -2093,12 +2093,12 @@ OCIO_ADD_TEST(Config, version) { OCIO_CHECK_THROW_WHAT(config->setVersion(2, 9), OCIO::Exception, "The minor version 9 is not supported for major version 2. " - "Maximum minor version is 4"); + "Maximum minor version is 5"); OCIO_CHECK_NO_THROW(config->setMajorVersion(2)); OCIO_CHECK_THROW_WHAT(config->setMinorVersion(9), OCIO::Exception, "The minor version 9 is not supported for major version 2. " - "Maximum minor version is 4"); + "Maximum minor version is 5"); } { @@ -3444,6 +3444,13 @@ OCIO_ADD_TEST(Config, display) std::istringstream is(myProfile); OCIO::ConstConfigRcPtr config; OCIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is)); + + // The active displays list is ignored if it would remove all displays. + OCIO_REQUIRE_EQUAL(config->getNumDisplays(), 6); + OCIO_CHECK_EQUAL(std::string(config->getDisplay(0)), std::string("sRGB_2")); + OCIO_CHECK_EQUAL(std::string(config->getDisplay(1)), std::string("sRGB_F")); + OCIO_CHECK_EQUAL(std::string(config->getDefaultDisplay()), "sRGB_2"); + OCIO_CHECK_THROW_WHAT(config->validate(), OCIO::Exception, "The list of active displays [ABCDEF] from the config file is invalid."); @@ -3562,6 +3569,7 @@ OCIO_ADD_TEST(Config, view) OCIO::ConstConfigRcPtr config; OCIO_CHECK_NO_THROW(config = OCIO::Config::CreateFromStream(is)); OCIO_CHECK_EQUAL(std::string(config->getDefaultView("sRGB_1")), "View_1"); + // The active views list is ignored, for a display, if it would remove all views. OCIO_REQUIRE_EQUAL(config->getNumViews("sRGB_1"), 2); OCIO_CHECK_EQUAL(std::string(config->getView("sRGB_1", 0)), "View_1"); OCIO_CHECK_EQUAL(std::string(config->getView("sRGB_1", 1)), "View_2"); @@ -3749,6 +3757,112 @@ OCIO_ADD_TEST(Config, display_view_order) OCIO_CHECK_EQUAL(std::string(config->getView("sRGB_B", 1)), "View_1"); } +OCIO_ADD_TEST(Config, active_displayview_lists) +{ + OCIO::ConfigRcPtr config = OCIO::Config::CreateRaw()->createEditableCopy(); + + // Test add. + OCIO_CHECK_EQUAL(config->getNumActiveDisplays(), 0); + OCIO_CHECK_EQUAL(config->getNumActiveViews(), 0); + config->addActiveDisplay("sRGB"); + config->addActiveDisplay("Display P3"); + config->addActiveView("v1"); + config->addActiveView("v2"); + + // Test getter. + OCIO_CHECK_EQUAL(config->getNumActiveDisplays(), 2); + OCIO_CHECK_EQUAL(std::string(config->getActiveDisplay(0)), "sRGB"); + OCIO_CHECK_EQUAL(std::string(config->getActiveDisplay(1)), "Display P3"); + OCIO_CHECK_EQUAL(config->getNumActiveViews(), 2); + OCIO_CHECK_EQUAL(std::string(config->getActiveView(0)), "v1"); + OCIO_CHECK_EQUAL(std::string(config->getActiveView(1)), "v2"); + + // Trying to add one that is already present doesn't add one, but does not throw. + OCIO_CHECK_NO_THROW(config->addActiveDisplay("sRGB")); + OCIO_CHECK_NO_THROW(config->addActiveView("v1")); + OCIO_CHECK_EQUAL(config->getNumActiveDisplays(), 2); + OCIO_CHECK_EQUAL(config->getNumActiveViews(), 2); + + // Test commas may be used. + config->setActiveDisplays("sRGB:01, \"Name, with comma\", \"Quoted name\""); + OCIO_CHECK_EQUAL(config->getNumActiveDisplays(), 3); + OCIO_CHECK_EQUAL(std::string(config->getActiveDisplay(0)), "sRGB:01"); + OCIO_CHECK_EQUAL(std::string(config->getActiveDisplay(1)), "Name, with comma"); + config->setActiveViews("v:01, \"View, with comma\", \"Quoted view\""); + OCIO_CHECK_EQUAL(config->getNumActiveViews(), 3); + OCIO_CHECK_EQUAL(std::string(config->getActiveView(0)), "v:01"); + OCIO_CHECK_EQUAL(std::string(config->getActiveView(1)), "View, with comma"); + + // Test remove. + config->removeActiveDisplay("Name, with comma"); + OCIO_CHECK_EQUAL(config->getNumActiveDisplays(), 2); + OCIO_CHECK_EQUAL(std::string(config->getActiveDisplay(1)), "Quoted name"); + config->removeActiveView("View, with comma"); + OCIO_CHECK_EQUAL(config->getNumActiveViews(), 2); + OCIO_CHECK_EQUAL(std::string(config->getActiveView(1)), "Quoted view"); + + // Test clear. + config->clearActiveDisplays(); + OCIO_CHECK_EQUAL(config->getNumActiveDisplays(), 0); + config->clearActiveViews(); + OCIO_CHECK_EQUAL(config->getNumActiveViews(), 0); + + // Trying to remove one that doesn't exist throws. + OCIO_CHECK_THROW_WHAT(config->removeActiveDisplay("not found"), + OCIO::Exception, + "Active display could not be removed from config"); + OCIO_CHECK_THROW_WHAT(config->removeActiveView("not found"), + OCIO::Exception, + "Active view could not be removed from config"); + + // Test setting an empty string behaves as expected. + config->setActiveDisplays(""); + OCIO_CHECK_EQUAL(config->getNumActiveDisplays(), 0); + config->addActiveDisplay("sRGB"); + OCIO_CHECK_EQUAL(config->getNumActiveDisplays(), 1); + config->setActiveViews(""); + OCIO_CHECK_EQUAL(config->getNumActiveViews(), 0); + config->addActiveView("v1"); + OCIO_CHECK_EQUAL(config->getNumActiveViews(), 1); + + // Test commas may be serialized and restored. + { + config->setActiveDisplays("sRGB:01, \"Name, with comma\", \"Quoted name\""); + config->setActiveViews("v:01, \"View, with comma\", \"Quoted view\""); + std::ostringstream oss; + config->serialize(oss); + std::istringstream iss; + iss.str(oss.str()); + OCIO::ConstConfigRcPtr config2 = OCIO::Config::CreateFromStream(iss); + OCIO_CHECK_EQUAL(config2->getNumActiveDisplays(), 3); + OCIO_CHECK_EQUAL(std::string(config2->getActiveDisplay(0)), "sRGB:01"); + OCIO_CHECK_EQUAL(std::string(config2->getActiveDisplay(1)), "Name, with comma"); + OCIO_CHECK_EQUAL(std::string(config2->getActiveDisplay(2)), "Quoted name"); + OCIO_CHECK_EQUAL(config2->getNumActiveViews(), 3); + OCIO_CHECK_EQUAL(std::string(config2->getActiveView(0)), "v:01"); + OCIO_CHECK_EQUAL(std::string(config2->getActiveView(1)), "View, with comma"); + OCIO_CHECK_EQUAL(std::string(config2->getActiveView(2)), "Quoted view"); + } + + // Check how an active list that uses colons as the separator is serialized. + // Turns out these are serialized as commas, so this was never a viable method to + // set an active list to handle use of commas in names. (It would be ok for use in + // the env. var., but not in the config itself.) + { + config->setActiveDisplays("sRGB01 : Name : \"Quoted name\""); + config->setActiveViews("v01:View: \"Quoted view\""); + std::ostringstream oss; + config->serialize(oss); + std::istringstream iss; + iss.str(oss.str()); + OCIO::ConstConfigRcPtr config2 = OCIO::Config::CreateFromStream(iss); + OCIO_CHECK_EQUAL(config2->getNumActiveDisplays(), 3); + OCIO_CHECK_EQUAL(std::string(config2->getActiveDisplays()), "sRGB01, Name, Quoted name"); + OCIO_CHECK_EQUAL(config2->getNumActiveViews(), 3); + OCIO_CHECK_EQUAL(std::string(config2->getActiveViews()), "v01, View, Quoted view"); + } +} + OCIO_ADD_TEST(Config, log_serialization) { { diff --git a/tests/cpu/ParseUtils_tests.cpp b/tests/cpu/ParseUtils_tests.cpp index 42f20360c8..246fc92fcf 100644 --- a/tests/cpu/ParseUtils_tests.cpp +++ b/tests/cpu/ParseUtils_tests.cpp @@ -387,7 +387,12 @@ OCIO_ADD_TEST(ParseUtils, string_vec_to_int_vec) OCIO_ADD_TEST(ParseUtils, split_string_env_style) { + // For look parsing, the split needs to always return a result, even if empty. StringUtils::StringVec outputvec; + outputvec = OCIO::SplitStringEnvStyle(""); + OCIO_CHECK_EQUAL(1, outputvec.size()); + outputvec.clear(); + outputvec = OCIO::SplitStringEnvStyle("This:is:a:test"); OCIO_CHECK_EQUAL(4, outputvec.size()); OCIO_CHECK_EQUAL("This", outputvec[0]); @@ -395,13 +400,15 @@ OCIO_ADD_TEST(ParseUtils, split_string_env_style) OCIO_CHECK_EQUAL("a", outputvec[2]); OCIO_CHECK_EQUAL("test", outputvec[3]); outputvec.clear(); - outputvec = OCIO::SplitStringEnvStyle(" This : is : a: test "); + + outputvec = OCIO::SplitStringEnvStyle(" \"This\" : is : a: test "); OCIO_CHECK_EQUAL(4, outputvec.size()); OCIO_CHECK_EQUAL("This", outputvec[0]); OCIO_CHECK_EQUAL("is", outputvec[1]); OCIO_CHECK_EQUAL("a", outputvec[2]); OCIO_CHECK_EQUAL("test", outputvec[3]); outputvec.clear(); + outputvec = OCIO::SplitStringEnvStyle(" This , is , a, test "); OCIO_CHECK_EQUAL(4, outputvec.size()); OCIO_CHECK_EQUAL("This", outputvec[0]); @@ -409,16 +416,90 @@ OCIO_ADD_TEST(ParseUtils, split_string_env_style) OCIO_CHECK_EQUAL("a", outputvec[2]); OCIO_CHECK_EQUAL("test", outputvec[3]); outputvec.clear(); + outputvec = OCIO::SplitStringEnvStyle("This:is , a:test "); OCIO_CHECK_EQUAL(2, outputvec.size()); OCIO_CHECK_EQUAL("This:is", outputvec[0]); OCIO_CHECK_EQUAL("a:test", outputvec[1]); outputvec.clear(); + outputvec = OCIO::SplitStringEnvStyle(",,"); OCIO_CHECK_EQUAL(3, outputvec.size()); OCIO_CHECK_EQUAL("", outputvec[0]); OCIO_CHECK_EQUAL("", outputvec[1]); OCIO_CHECK_EQUAL("", outputvec[2]); + + outputvec = OCIO::SplitStringEnvStyle(" \"This : is \": a: test "); + OCIO_CHECK_EQUAL(3, outputvec.size()); + OCIO_CHECK_EQUAL("This : is ", outputvec[0]); + OCIO_CHECK_EQUAL("a", outputvec[1]); + OCIO_CHECK_EQUAL("test", outputvec[2]); + + OCIO_CHECK_THROW_WHAT( OCIO::SplitStringEnvStyle(" This : is \": a: test "), + OCIO::Exception, + "The string 'This : is \": a: test' is not correctly formatted. " + "It is missing a closing quote."); + + OCIO_CHECK_THROW_WHAT( OCIO::SplitStringEnvStyle(" This : is : a: test \""), + OCIO::Exception, + "The string 'This : is : a: test \"' is not correctly formatted. " + "It is missing a closing quote."); + + outputvec = OCIO::SplitStringEnvStyle(" This : is \": a: test \""); + OCIO_CHECK_EQUAL(2, outputvec.size()); + OCIO_CHECK_EQUAL("This", outputvec[0]); + OCIO_CHECK_EQUAL("is \": a: test \"" , outputvec[1]); + + outputvec = OCIO::SplitStringEnvStyle(" \"This : is \", a, test "); + OCIO_CHECK_EQUAL(3, outputvec.size()); + OCIO_CHECK_EQUAL("This : is ", outputvec[0]); + OCIO_CHECK_EQUAL("a", outputvec[1]); + OCIO_CHECK_EQUAL("test", outputvec[2]); + + // If the string contains a comma, it is chosen as the separator character rather than + // the colon (even if it is within quotes and therefore not used as such). + outputvec = OCIO::SplitStringEnvStyle(" \"This , is \": a: test "); + OCIO_CHECK_EQUAL(1, outputvec.size()); + OCIO_CHECK_EQUAL("\"This , is \": a: test", outputvec[0]); + + outputvec = OCIO::SplitStringEnvStyle(" \"This , is \": a, test "); + OCIO_CHECK_EQUAL(2, outputvec.size()); + OCIO_CHECK_EQUAL("\"This , is \": a", outputvec[0]); + OCIO_CHECK_EQUAL("test", outputvec[1]); +} + +OCIO_ADD_TEST(ParseUtils, join_string_env_style) +{ + StringUtils::StringVec outputvec {"This", "is", "a", "test"}; + + OCIO_CHECK_EQUAL( "This, is, a, test", OCIO::JoinStringEnvStyle(outputvec) ); + outputvec.clear(); + + OCIO_CHECK_EQUAL( "", OCIO::JoinStringEnvStyle(outputvec) ); + outputvec.clear(); + + outputvec = { "This:is", "a:test" }; + OCIO_CHECK_EQUAL( "\"This:is\", \"a:test\"", OCIO::JoinStringEnvStyle(outputvec) ); + outputvec.clear(); + + outputvec = { "", "", "" }; + OCIO_CHECK_EQUAL( ", , ", OCIO::JoinStringEnvStyle(outputvec) ); + outputvec.clear(); + + outputvec = { "This : is", "a: test" }; + OCIO_CHECK_EQUAL( "\"This : is\", \"a: test\"", OCIO::JoinStringEnvStyle(outputvec) ); + outputvec.clear(); + + outputvec = { "This", "is \": a: test" }; + OCIO_CHECK_EQUAL( "This, \"is \": a: test\"", OCIO::JoinStringEnvStyle(outputvec) ); + + outputvec = { "\"This, is, a, string\"", "this, one, too" }; + OCIO_CHECK_EQUAL( "\"This, is, a, string\", \"this, one, too\"" , + OCIO::JoinStringEnvStyle(outputvec) ); + + outputvec = { "This", "is: ", "\"a very good,\"", " fine, helpful, and useful ", "test" }; + OCIO_CHECK_EQUAL( "This, \"is: \", \"a very good,\", \" fine, helpful, and useful \", test", + OCIO::JoinStringEnvStyle(outputvec) ); } OCIO_ADD_TEST(ParseUtils, intersect_string_vecs_case_ignore) diff --git a/tests/cpu/fileformats/FileFormatCTF_tests.cpp b/tests/cpu/fileformats/FileFormatCTF_tests.cpp index b9b2662165..25cc87a4d2 100644 --- a/tests/cpu/fileformats/FileFormatCTF_tests.cpp +++ b/tests/cpu/fileformats/FileFormatCTF_tests.cpp @@ -1454,7 +1454,7 @@ OCIO_ADD_TEST(FileFormatCTF, difficult_xml_unknown_elements) "(37): Unrecognized element 'just_ignore' where its parent is 'ProcessList' (8): Unknown element", "(69): Unrecognized element 'just_ignore' where its parent is 'Description' (66)", "(70): Unrecognized element 'just_ignore' where its parent is 'just_ignore' (69)", - "(75): Unrecognized element 'Matrix' where its parent is 'LUT1D' (43): 'Matrix' not allowed in this element", + "(75): Unrecognized element 'Matrix' where its parent is 'LUT1D' (", // Line number is expat library version dependent. "(76): Unrecognized element 'Description' where its parent is 'Matrix' (75)", "(77): Unrecognized element 'Array' where its parent is 'Matrix' (75)" }; diff --git a/tests/data/files/configs/context_test1/context_test1_windows.ocioz b/tests/data/files/configs/context_test1/context_test1_windows.ocioz index 091b68e52d..eb52db3a76 100644 Binary files a/tests/data/files/configs/context_test1/context_test1_windows.ocioz and b/tests/data/files/configs/context_test1/context_test1_windows.ocioz differ diff --git a/tests/python/ColorSpaceTest.py b/tests/python/ColorSpaceTest.py index 0860e7a3f0..6fae105b63 100644 --- a/tests/python/ColorSpaceTest.py +++ b/tests/python/ColorSpaceTest.py @@ -42,6 +42,9 @@ def test_copy(self): self.colorspace.setTransform(direction=OCIO.COLORSPACE_DIR_FROM_REFERENCE, transform=mat) self.colorspace.addAlias('alias') self.colorspace.addCategory('cat') + self.colorspace.setInteropID('data') + self.colorspace.setInterchangeAttribute('amf_transform_ids', 'urn:ampas:aces:transformId:v1.5:ACEScsc.Academy.CG_to_ACES.a1.0.3') + self.colorspace.setInterchangeAttribute('icc_profile_name', 'sRGB IEC61966-2.1') other = copy.deepcopy(self.colorspace) self.assertFalse(other is self.colorspace) @@ -59,6 +62,11 @@ def test_copy(self): self.assertTrue(other.getTransform(OCIO.COLORSPACE_DIR_FROM_REFERENCE).equals(self.colorspace.getTransform(OCIO.COLORSPACE_DIR_FROM_REFERENCE))) self.assertEqual(list(other.getAliases()), list(self.colorspace.getAliases())) self.assertEqual(list(other.getCategories()), list(self.colorspace.getCategories())) + self.assertEqual(other.getInteropID(), self.colorspace.getInteropID()) + self.assertEqual(other.getInterchangeAttribute('amf_transform_ids'), self.colorspace.getInterchangeAttribute('amf_transform_ids')) + self.assertEqual(other.getInterchangeAttribute('icc_profile_name'), self.colorspace.getInterchangeAttribute('icc_profile_name')) + self.assertEqual(other.getInterchangeAttributes(), self.colorspace.getInterchangeAttributes()) + self.assertListEqual(list(other.getInterchangeAttributes().items()), list(self.colorspace.getInterchangeAttributes().items())) def test_allocation(self): """ @@ -279,6 +287,10 @@ def test_constructor_without_parameter(self): self.assertFalse(cs.isData()) self.assertEqual(cs.getAllocation(), OCIO.ALLOCATION_UNIFORM) self.assertEqual(cs.getAllocationVars(), []) + self.assertEqual(cs.getInteropID(), '') + self.assertEqual(cs.getInterchangeAttribute("amf_transform_ids"), '') + self.assertEqual(cs.getInterchangeAttribute("icc_profile_name"), '') + self.assertEqual(len(cs.getInterchangeAttributes()), 0) def test_data(self): """ @@ -421,6 +433,195 @@ def test_aliases(self): self.assertEqual(len(aliases), 0) self.assertFalse(cs.hasAlias('alias1')) + def test_interop_id(self): + """ + Test the setInteropID() and getInteropID() methods. + """ + + # Test default value (should be empty). + self.assertEqual(self.colorspace.getInteropID(), '') + + # Test setting and getting a simple interop ID. + test_id = 'lin_ap0_scene' + self.colorspace.setInteropID(test_id) + self.assertEqual(self.colorspace.getInteropID(), test_id) + + # Test setting and getting a different interop ID. + test_id2 = 'srgb_ap1_scene' + self.colorspace.setInteropID(test_id2) + self.assertEqual(self.colorspace.getInteropID(), test_id2) + + # Test setting empty string. + self.colorspace.setInteropID('') + self.assertEqual(self.colorspace.getInteropID(), '') + + # Test setting None (should convert to empty string). + self.colorspace.setInteropID('something') + self.colorspace.setInteropID(None) + self.assertEqual(self.colorspace.getInteropID(), '') + + # Test wrong type (should raise TypeError). + with self.assertRaises(TypeError): + self.colorspace.setInteropID(123) + + with self.assertRaises(TypeError): + self.colorspace.setInteropID(['list']) + + # Test valid InteropID with one colon (not at the end). + valid_with_colon = 'namespace:cs_name' + self.colorspace.setInteropID(valid_with_colon) + self.assertEqual(self.colorspace.getInteropID(), valid_with_colon) + + # Test invalid InteropID with multiple colons. + with self.assertRaises(Exception) as context: + self.colorspace.setInteropID('name:space:cs_name') + self.assertIn("Only one ':' is allowed to separate the namespace and the color space.", + str(context.exception)) + + # Test invalid InteropID with colon at the end. + with self.assertRaises(Exception) as context: + self.colorspace.setInteropID('namespace:') + self.assertIn("If ':' is used, both the namespace and the color space parts must be non-empty.", + str(context.exception)) + + # Test invalid InteropID with non-ASCII characters. + with self.assertRaises(Exception) as context: + self.colorspace.setInteropID('café_scene') # Contains é (UTF-8) + self.assertIn("contains invalid characters.", str(context.exception)) + + with self.assertRaises(Exception) as context: + self.colorspace.setInteropID('space±_name') # Contains ± (ANSI 0xB1) + self.assertIn("contains invalid characters.", str(context.exception)) + + def test_interchange_attributes(self): + """ + Test the setInterchangeAttribute() and getInterchangeAttribute() methods. + """ + # amf_transform_ids + + # Test default value (should be empty). + self.assertEqual(self.colorspace.getInterchangeAttribute('amf_transform_ids'), '') + + # Test setting and getting a single amf transform ID. + single_id = 'urn:ampas:aces:transformId:v1.5:ACEScsc.Academy.CG_to_ACES.a1.0.3' + self.colorspace.setInterchangeAttribute('amf_transform_ids', single_id) + self.assertEqual(self.colorspace.getInterchangeAttribute('amf_transform_ids'), single_id) + self.assertEqual(len(self.colorspace.getInterchangeAttributes()), 1) + self.assertEqual(self.colorspace.getInterchangeAttributes()["amf_transform_ids"], single_id) + + # Test setting and getting multiple transform IDs (newline-separated). + multiple_ids = ('urn:ampas:aces:transformId:v1.5:ACEScsc.Academy.CG_to_ACES.a1.0.3\n' + 'urn:ampas:aces:transformId:v1.5:ACEScsc.Academy.ACES_to_CG.a1.0.3\n' + 'urn:ampas:aces:transformId:v1.5:RRT.a1.0.3') + self.colorspace.setInterchangeAttribute('amf_transform_ids', multiple_ids) + self.assertEqual(self.colorspace.getInterchangeAttribute('amf_transform_ids'), multiple_ids) + self.assertEqual(len(self.colorspace.getInterchangeAttributes()), 1) + self.assertEqual(self.colorspace.getInterchangeAttributes()["amf_transform_ids"], multiple_ids) + + # Test setting empty string. + self.colorspace.setInterchangeAttribute('amf_transform_ids', '') + self.assertEqual(self.colorspace.getInterchangeAttribute('amf_transform_ids'), '') + self.assertEqual(len(self.colorspace.getInterchangeAttributes()), 0) + + # Test setting None (should convert to empty string). + self.colorspace.setInterchangeAttribute('amf_transform_ids', 'something') + self.colorspace.setInterchangeAttribute('amf_transform_ids', None) + self.assertEqual(self.colorspace.getInterchangeAttribute('amf_transform_ids'), '') + self.assertEqual(len(self.colorspace.getInterchangeAttributes()), 0) + + # Test with different line endings. + mixed_endings = 'id1\nid2\rid3\r\nid4' + self.colorspace.setInterchangeAttribute('amf_transform_ids', mixed_endings) + self.assertEqual(self.colorspace.getInterchangeAttribute('amf_transform_ids'), mixed_endings) + + # Test with leading/trailing whitespace. + whitespace_ids = ' \n id1 \n id2 \n ' + self.colorspace.setInterchangeAttribute('amf_transform_ids', whitespace_ids) + self.assertEqual(self.colorspace.getInterchangeAttribute('amf_transform_ids'), whitespace_ids) + + # Test wrong type (should raise TypeError). + with self.assertRaises(TypeError): + self.colorspace.setInterchangeAttribute('amf_transform_ids', 123) + + with self.assertRaises(TypeError): + self.colorspace.setInterchangeAttribute('amf_transform_ids', ['list', 'of', 'ids']) + + # clear amf_transform_ids for the next test + self.colorspace.setInterchangeAttribute('amf_transform_ids', '') + self.assertEqual(len(self.colorspace.getInterchangeAttributes()), 0) + + # icc_profile_name + + # Test default value (should be empty). + self.assertEqual(self.colorspace.getInterchangeAttribute('icc_profile_name'), '') + + # Test setting and getting a simple profile name. + profile_name = 'sRGB IEC61966-2.1' + self.colorspace.setInterchangeAttribute('icc_profile_name', profile_name) + self.assertEqual(self.colorspace.getInterchangeAttribute('icc_profile_name'), profile_name) + self.assertEqual(len(self.colorspace.getInterchangeAttributes()), 1) + self.assertEqual(self.colorspace.getInterchangeAttributes()["icc_profile_name"], profile_name) + + # Test setting and getting a different profile name. + profile_name2 = 'Adobe RGB (1998)' + self.colorspace.setInterchangeAttribute('icc_profile_name', profile_name2) + self.assertEqual(self.colorspace.getInterchangeAttribute('icc_profile_name'), profile_name2) + self.assertEqual(len(self.colorspace.getInterchangeAttributes()), 1) + self.assertEqual(self.colorspace.getInterchangeAttributes()["icc_profile_name"], profile_name2) + + # Test with a more complex profile name. + complex_name = 'Display P3 - Apple Cinema Display (Calibrated 2023-01-15)' + self.colorspace.setInterchangeAttribute('icc_profile_name', complex_name) + self.assertEqual(self.colorspace.getInterchangeAttribute('icc_profile_name'), complex_name) + + # Test setting empty string. + self.colorspace.setInterchangeAttribute('icc_profile_name', '') + self.assertEqual(self.colorspace.getInterchangeAttribute('icc_profile_name'), '') + self.assertEqual(len(self.colorspace.getInterchangeAttributes()), 0) + + # Test setting None (should convert to empty string). + self.colorspace.setInterchangeAttribute('icc_profile_name', 'something') + self.colorspace.setInterchangeAttribute('icc_profile_name', None) + self.assertEqual(self.colorspace.getInterchangeAttribute('icc_profile_name'), '') + self.assertEqual(len(self.colorspace.getInterchangeAttributes()), 0) + + # Test with special characters and numbers. + special_name = 'ProPhoto RGB v2.0 (γ=1.8) [Custom Profile #123]' + self.colorspace.setInterchangeAttribute('icc_profile_name', special_name) + self.assertEqual(self.colorspace.getInterchangeAttribute('icc_profile_name'), special_name) + + # Test with Unicode characters. + unicode_name = 'Профиль RGB γ=2.2' + self.colorspace.setInterchangeAttribute('icc_profile_name', unicode_name) + self.assertEqual(self.colorspace.getInterchangeAttribute('icc_profile_name'), unicode_name) + + # Test wrong type (should raise TypeError). + with self.assertRaises(TypeError): + self.colorspace.setInterchangeAttribute('icc_profile_name', 123) + + with self.assertRaises(TypeError): + self.colorspace.setInterchangeAttribute('icc_profile_name', ['profile', 'name']) + + # test both interchange attributes together. + + self.colorspace.setInterchangeAttribute('icc_profile_name', 'icc_value') + self.colorspace.setInterchangeAttribute('amf_transform_ids', 'amf_value') + self.assertEqual(len(self.colorspace.getInterchangeAttributes()), 2) + self.assertEqual(self.colorspace.getInterchangeAttribute('icc_profile_name'), 'icc_value') + self.assertEqual(self.colorspace.getInterchangeAttribute('amf_transform_ids'), 'amf_value') + + # unsupported interchange key + + # test that setting an unsupported key raises. + with self.assertRaises(Exception) as context: + self.colorspace.setInterchangeAttribute('this_should_fail', 'foo42') + self.assertIn("Unknown attribute name 'this_should_fail'", str(context.exception)) + + # test that getting an unsupported key raises. + with self.assertRaises(Exception) as context: + self.colorspace.getInterchangeAttribute('this_should_fail') + self.assertIn("Unknown attribute name 'this_should_fail'", str(context.exception)) + def test_is_colorspace_linear(self): """ Test isColorSpaceLinear. @@ -634,7 +835,7 @@ def test_display_referred(self, cfg, cs_name, expected_value): def test_processor_to_known_colorspace(self): - CONFIG = """ocio_profile_version: 2 + CONFIG = """ocio_profile_version: 2.0 roles: default: raw @@ -690,12 +891,14 @@ def test_processor_to_known_colorspace(self): - ! name: ACES cg description: An ACEScg space with an unusual spelling. + interop_id: lin_ap1_scene isdata: false to_scene_reference: ! {style: ACEScg_to_ACES2065-1} - ! name: Linear ITU-R BT.709 description: A linear Rec.709 space with an unusual spelling. + interop_id: "mycompany:led_wall_1" isdata: false from_scene_reference: ! name: AP0 to Linear Rec.709 (sRGB) diff --git a/tests/python/ConfigTest.py b/tests/python/ConfigTest.py index 78327a582d..57b12b7d7c 100644 --- a/tests/python/ConfigTest.py +++ b/tests/python/ConfigTest.py @@ -1381,6 +1381,72 @@ def test_roles(self): # Test the new value of the role color_picking. self.assertEqual(config.getRoleColorSpace("color_picking"), "ACEScct") + def test_active__displayview_lists(self): + config = OCIO.Config.CreateRaw() + + # Test add. + self.assertEqual(len(config.getActiveDisplays()), 0) + self.assertEqual(len(config.getActiveViews()), 0) + config.addActiveDisplay("sRGB") + config.addActiveDisplay("Display P3") + config.addActiveView("v1") + config.addActiveView("v2") + + # Test getters. + self.assertEqual(len(config.getActiveDisplays()), 2) + self.assertEqual(config.getActiveDisplays()[0], "sRGB") + self.assertEqual(config.getActiveDisplays()[1], "Display P3") + self.assertEqual(len(config.getActiveViews()), 2) + self.assertEqual(config.getActiveViews()[0], "v1") + self.assertEqual(config.getActiveViews()[1], "v2") + + # Test that add doesn't throw on dupes. + config.addActiveDisplay("sRGB") + self.assertEqual(config.getNumActiveDisplays(), 2) + config.addActiveView("v1") + self.assertEqual(config.getNumActiveViews(), 2) + + # Test commas may be used. + config.setActiveDisplays(displays='sRGB:01, "Name, with comma", "Quoted name"') + self.assertEqual(config.getNumActiveDisplays(), 3) + self.assertEqual(config.getActiveDisplays()[0], "sRGB:01") + self.assertEqual(config.getActiveDisplays()[1], "Name, with comma") + config.setActiveViews(views='v:01, "View, with comma", "Quoted view"') + self.assertEqual(config.getNumActiveViews(), 3) + self.assertEqual(config.getActiveViews()[0], "v:01") + self.assertEqual(config.getActiveViews()[1], "View, with comma") + + # Test remove. + config.removeActiveDisplay(display="Name, with comma") + self.assertEqual(config.getActiveDisplays()[1], "Quoted name") + self.assertEqual(config.getNumActiveDisplays(), 2) + config.removeActiveView(view="View, with comma") + self.assertEqual(config.getActiveViews()[1], "Quoted view") + self.assertEqual(config.getNumActiveViews(), 2) + + # Test clear. + config.clearActiveDisplays() + self.assertEqual(config.getNumActiveDisplays(), 0) + config.clearActiveViews() + self.assertEqual(config.getNumActiveViews(), 0) + + # Trying to remove one that doesn't exist throws. + with self.assertRaises(OCIO.Exception): + config.removeActiveDisplay("not found") + with self.assertRaises(OCIO.Exception): + config.removeActiveView("not found") + + # Test setting an empty string behaves as expected. + config.setActiveDisplays("") + self.assertEqual(config.getNumActiveDisplays(), 0) + config.addActiveDisplay(display="sRGB") + self.assertEqual(config.getNumActiveDisplays(), 1) + config.setActiveViews("") + self.assertEqual(config.getNumActiveViews(), 0) + config.addActiveView(view="v1") + self.assertEqual(config.getNumActiveViews(), 1) + + class ConfigVirtualWithActiveDisplayTest(unittest.TestCase): def setUp(self): self.cfg_active_display = OCIO.Config.CreateFromStream(