diff --git a/.github/workflows/ci_workflow.yml b/.github/workflows/ci_workflow.yml index 62ea921776..82a186d04d 100644 --- a/.github/workflows/ci_workflow.yml +++ b/.github/workflows/ci_workflow.yml @@ -459,7 +459,7 @@ jobs: if: | github.event_name == 'push' || github.event.pull_request.head.repo.full_name != github.repository - runs-on: macos-13 + runs-on: macos-15-intel strategy: matrix: build: [1, 2, 3, 4, 5] diff --git a/.github/workflows/wheel_workflow.yml b/.github/workflows/wheel_workflow.yml index ed83e68457..8fa6ce2f66 100644 --- a/.github/workflows/wheel_workflow.yml +++ b/.github/workflows/wheel_workflow.yml @@ -101,10 +101,10 @@ 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 + - 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 # ------------------------------------------------------------------- @@ -128,10 +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 + - build: CPython 3.14 64 bits manylinux2014 + manylinux: manylinux2014 + python: cp314-manylinux_x86_64 + arch: x86_64 steps: - uses: actions/checkout@v4 @@ -142,7 +142,7 @@ jobs: python-version: '3.11' - name: Build wheels - uses: pypa/cibuildwheel@v3.1.4 + uses: pypa/cibuildwheel@v3.3.0 env: CIBW_BUILD: ${{ matrix.python }} CIBW_ARCHS: ${{ matrix.arch }} @@ -190,10 +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 + - build: CPython 3.14 ARM 64 bits manylinux2014 + manylinux: manylinux2014 + python: cp314-manylinux_aarch64 + arch: aarch64 steps: - uses: actions/checkout@v4 @@ -204,7 +204,7 @@ jobs: python-version: '3.11' - name: Build wheels - uses: pypa/cibuildwheel@v3.1.4 + uses: pypa/cibuildwheel@v3.3.0 env: CIBW_BUILD: ${{ matrix.python }} CIBW_ARCHS: ${{ matrix.arch }} @@ -221,7 +221,7 @@ jobs: macos: name: Build wheels on macOS - runs-on: macos-13 + runs-on: macos-15-intel # Don't run on OCIO forks if: | github.event_name != 'schedule' || @@ -247,9 +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 + - build: CPython 3.14 64 bits + python: cp314-macosx_x86_64 + arch: x86_64 steps: - uses: actions/checkout@v4 @@ -264,7 +264,7 @@ jobs: brew uninstall --ignore-dependencies openexr imath || true - name: Build wheels - uses: pypa/cibuildwheel@v3.1.4 + uses: pypa/cibuildwheel@v3.3.0 env: CIBW_BUILD: ${{ matrix.python }} CIBW_ARCHS: ${{ matrix.arch }} @@ -306,9 +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 + - build: CPython 3.14 ARM 64 bits + python: cp314-macosx_arm64 + arch: arm64 steps: - uses: actions/checkout@v4 @@ -319,7 +319,7 @@ jobs: python-version: '3.11' - name: Build wheels - uses: pypa/cibuildwheel@v3.1.4 + uses: pypa/cibuildwheel@v3.3.0 env: CIBW_BUILD: ${{ matrix.python }} CIBW_ARCHS: ${{ matrix.arch }} @@ -361,9 +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 + - build: CPython 3.14 64 bits + python: cp314-win_amd64 + arch: AMD64 steps: - uses: actions/checkout@v4 @@ -374,7 +374,7 @@ jobs: python-version: '3.11' - name: Build wheels - uses: pypa/cibuildwheel@v3.1.4 + uses: pypa/cibuildwheel@v3.3.0 env: CIBW_BUILD: ${{ matrix.python }} CIBW_ARCHS: ${{ matrix.arch }} diff --git a/docs/site/homepage/data/en/contact.yml b/docs/site/homepage/data/en/contact.yml index 624ddcf0e3..f0b6e8b9e8 100644 --- a/docs/site/homepage/data/en/contact.yml +++ b/docs/site/homepage/data/en/contact.yml @@ -9,8 +9,8 @@ contact: contact_list: - icon : ti-comment # themify icon pack : https://themify.me/themify-icons name: "Slack:" - info : "OpenColorIO" - link: "http://slack.opencolorio.org" + info : "#OpenColorIO" + link: "https://academysoftwarefdn.slack.com" - icon : ti-email # themify icon pack : https://themify.me/themify-icons name: "OCIO-User:" diff --git a/docs/site/homepage/data/en/news.yml b/docs/site/homepage/data/en/news.yml index 71a4626961..fe04515deb 100644 --- a/docs/site/homepage/data/en/news.yml +++ b/docs/site/homepage/data/en/news.yml @@ -2,26 +2,26 @@ news: enable : true image : images/disney_insideout.jpeg - title : OCIO v2 is here! + title : OCIO v2.5.0 is here! content : "Check out our [guides](https://opencolorio.readthedocs.io/en/latest/releases/_index.html#releases) for upgrading. Here are just a few highlights of new features:" caption: "Image courtesy of Disney (c)" feature_item : # feature item loop - - title : New GPU Renderer - icon : ti-world # themify icon pack : https://themify.me/themify-icons - content : The GPU renderer now matches the CPU and may be used to render final frames! + - title : Config Merging + icon : ti-files # themify icon pack : https://themify.me/themify-icons + content : This much-anticipated feature is now in Preview. Please provide feedback on use cases and workflows. Merging enables user and application control over things like name and alias conflicts, color space duplication, and much more. # feature item loop - - title : Better User Experience - icon : ti-brush # themify icon pack : https://themify.me/themify-icons - content : Config authors have new tools to organize color spaces by category, hide color spaces, and define hierarchical menus. New File and Viewing Rules allow more powerful default behaviors. + - title : Vulkan GPU Support + icon : ti-image # themify icon pack : https://themify.me/themify-icons + content : Support has been added for the Khronos Vulkan graphics API standard. The OCIO GPU renderer may now be used within applications that use Vulkan to get the most out of the latest GPUs. # feature item loop - - title : Improved ACES Support - icon : ti-stats-up # themify icon pack : https://themify.me/themify-icons - content : ACES Output Transforms no longer use 3d-LUTs and are accurate even for extreme exposure values. There is also full read and write support for the Academy/ASC Common LUT Format (CLF). + - title : Full ACES 2.0 Support + icon : ti-brush # themify icon pack : https://themify.me/themify-icons + content : With the inclusion of ACES 2.0 Built-In configs, our ACES 2.0 support is now feature complete. This allows use of the ACES 2.0 Output Transforms as OCIO views. # feature item loop - - title : Analysis Tools + - title : New Color Space Attributes icon : ti-panel # themify icon pack : https://themify.me/themify-icons - content : There are new command line tools for evaluating LUTs, converting to CLF, serializing OCIO Processors, and more! \ No newline at end of file + content : OCIO now supports the Color Interop ID developed by the Color Interop Forum, as well as attributes for ICC and AMF. Please see the latest config release for example usage. \ No newline at end of file diff --git a/share/ci/scripts/windows/install_doxygen.sh b/share/ci/scripts/windows/install_doxygen.sh index a08783f765..7b4341d5f5 100755 --- a/share/ci/scripts/windows/install_doxygen.sh +++ b/share/ci/scripts/windows/install_doxygen.sh @@ -10,7 +10,7 @@ DOXYGEN_LOCATION="$1" choco install jq # Get the URL of the latest zip package for Doxygen. -url=$(curl -s 'https://api.github.com/repos/doxygen/doxygen/releases/latest' | jq -r '.assets[] | select(.name | test("doxygen-.*windows.x64.bin.zip")) | .browser_download_url') +url=$(curl -s 'https://api.github.com/repos/doxygen/doxygen/releases/latest' | jq -r '.assets[] | select(.name | test("doxygen-.*\\.x64\\.bin\\.zip")) | .browser_download_url') # Download the zip. mkdir $DOXYGEN_LOCATION diff --git a/share/cmake/modules/FindImath.cmake b/share/cmake/modules/FindImath.cmake index 6126b934f7..fc2086aad1 100644 --- a/share/cmake/modules/FindImath.cmake +++ b/share/cmake/modules/FindImath.cmake @@ -40,10 +40,8 @@ if(NOT OCIO_INSTALL_EXT_PACKAGES STREQUAL ALL) set(_Imath_REQUIRED_VARS Imath_LIBRARY) set(_Imath_LIB_VER "${Imath_FIND_VERSION_MAJOR}_${Imath_FIND_VERSION_MINOR}") - if(NOT DEFINED Imath_ROOT) - # Search for ImathConfig.cmake - find_package(Imath ${Imath_FIND_VERSION} CONFIG QUIET) - endif() + # Search for ImathConfig.cmake + find_package(Imath ${Imath_FIND_VERSION} CONFIG QUIET) if(Imath_FOUND) get_target_property(Imath_LIBRARY Imath::Imath LOCATION) diff --git a/share/cmake/modules/FindOSL.cmake b/share/cmake/modules/FindOSL.cmake index 5cefe410bc..54dc17fb73 100644 --- a/share/cmake/modules/FindOSL.cmake +++ b/share/cmake/modules/FindOSL.cmake @@ -29,22 +29,22 @@ ############################################################################### ### Try to find package ### -if(NOT DEFINED OSL_ROOT) - find_package(OSL ${OSL_FIND_VERSION} CONFIG QUIET) - - set(OSL_SHADERS_INCLUDE_DIR ${OSL_INCLUDE_DIR}/../share) - # Variable used by the OSL unit tests. - set(OSL_SHADERS_DIR ${OSL_SHADERS_INCLUDE_DIR}/OSL/shaders) - - include (FindPackageHandleStandardArgs) - find_package_handle_standard_args (OSL - REQUIRED_VARS - OSL_INCLUDE_DIR - OSL_LIB_DIR - VERSION_VAR - OSL_VERSION - ) -else() +find_package(OSL ${OSL_FIND_VERSION} CONFIG QUIET) + +set(OSL_SHADERS_INCLUDE_DIR ${OSL_INCLUDE_DIR}/../share) +# Variable used by the OSL unit tests. +set(OSL_SHADERS_DIR ${OSL_SHADERS_INCLUDE_DIR}/OSL/shaders) + +include (FindPackageHandleStandardArgs) +find_package_handle_standard_args (OSL + REQUIRED_VARS + OSL_INCLUDE_DIR + OSL_LIB_DIR + VERSION_VAR + OSL_VERSION +) + +if(NOT OSL_FOUND) set(OSL_INCLUDE_DIR ${OSL_ROOT}/include) set(OSL_VERSION_HEADER "${OSL_INCLUDE_DIR}/OSL/oslversion.h") diff --git a/share/cmake/modules/Findexpat.cmake b/share/cmake/modules/Findexpat.cmake index 1e715bb0cf..73eed21fb9 100644 --- a/share/cmake/modules/Findexpat.cmake +++ b/share/cmake/modules/Findexpat.cmake @@ -39,10 +39,8 @@ endif() if(NOT OCIO_INSTALL_EXT_PACKAGES STREQUAL ALL) set(_expat_REQUIRED_VARS expat_LIBRARY) - if(NOT DEFINED expat_ROOT) - # Search for expat-config.cmake - find_package(expat ${expat_FIND_VERSION} CONFIG QUIET) - endif() + # Search for expat-config.cmake + find_package(expat ${expat_FIND_VERSION} CONFIG QUIET) if(expat_FOUND) if (TARGET expat::libexpat) diff --git a/share/cmake/modules/Findminizip-ng.cmake b/share/cmake/modules/Findminizip-ng.cmake index 3609b0ef84..bf194c073d 100644 --- a/share/cmake/modules/Findminizip-ng.cmake +++ b/share/cmake/modules/Findminizip-ng.cmake @@ -45,10 +45,8 @@ if(CMAKE_BUILD_TYPE MATCHES "[Dd][Ee][Bb][Uu][Gg]") endif() if(NOT OCIO_INSTALL_EXT_PACKAGES STREQUAL ALL) - if(NOT DEFINED minizip-ng_ROOT) - # Search for minizip-ng-config.cmake - find_package(minizip-ng ${minizip-ng_FIND_VERSION} CONFIG QUIET) - endif() + # Search for minizip-ng-config.cmake + find_package(minizip-ng ${minizip-ng_FIND_VERSION} CONFIG QUIET) if (minizip-ng_FOUND) get_target_property(minizip-ng_INCLUDE_DIR MINIZIP::minizip-ng INTERFACE_INCLUDE_DIRECTORIES) diff --git a/share/cmake/modules/Findpybind11.cmake b/share/cmake/modules/Findpybind11.cmake index ccbb53e656..bfcb9a696e 100644 --- a/share/cmake/modules/Findpybind11.cmake +++ b/share/cmake/modules/Findpybind11.cmake @@ -24,10 +24,8 @@ ### Try to find package ### if(NOT OCIO_INSTALL_EXT_PACKAGES STREQUAL ALL) - if(NOT DEFINED pybind11_ROOT) - # Search for pybind11Config.cmake - find_package(pybind11 ${pybind11_FIND_VERSION} CONFIG QUIET) - endif() + # Search for pybind11Config.cmake + find_package(pybind11 ${pybind11_FIND_VERSION} CONFIG QUIET) if(NOT pybind11_FOUND) # Find include directory diff --git a/share/cmake/modules/Findyaml-cpp.cmake b/share/cmake/modules/Findyaml-cpp.cmake index d10bdb4f5e..95275bcea8 100644 --- a/share/cmake/modules/Findyaml-cpp.cmake +++ b/share/cmake/modules/Findyaml-cpp.cmake @@ -44,9 +44,7 @@ if(NOT OCIO_INSTALL_EXT_PACKAGES STREQUAL ALL) set(_yaml-cpp_REQUIRED_VARS yaml-cpp_LIBRARY) # Search for yaml-cpp-config.cmake - if(NOT DEFINED yaml-cpp_ROOT) - find_package(yaml-cpp ${yaml-cpp_FIND_VERSION} CONFIG ${quiet}) - endif() + find_package(yaml-cpp ${yaml-cpp_FIND_VERSION} CONFIG ${quiet}) if(yaml-cpp_FOUND) # Alias target for yaml-cpp < 0.8 compatibility diff --git a/src/OpenColorIO/fileformats/FileFormatICC.cpp b/src/OpenColorIO/fileformats/FileFormatICC.cpp index 4768101129..69c0145d8a 100755 --- a/src/OpenColorIO/fileformats/FileFormatICC.cpp +++ b/src/OpenColorIO/fileformats/FileFormatICC.cpp @@ -15,7 +15,6 @@ #include "ops/gamma/GammaOp.h" #include "ops/lut1d/Lut1DOp.h" #include "ops/matrix/MatrixOp.h" -#include "ops/range/RangeOp.h" #include "Platform.h" #include "transforms/FileTransform.h" @@ -794,13 +793,23 @@ LocalFileFormat::buildFileOps(OpRcPtrVec & ops, } } - // The matrix/TRC transform in the ICC profile converts display device code values to the - // CIE XYZ based version of the ICC profile connection space (PCS). - // However, in OCIO the most common use of an ICC monitor profile is as a display color space, - // and in that usage it is more natural for the XYZ to display code value transform to be called - // the forward direction. + // The matrix/TRC transform in the ICC profile converts display device code + // values to the CIE XYZ based version of the ICC profile connection space + // (PCS). However, in OCIO the most common use of an ICC monitor profile is + // as a display color space, and in that usage it is more natural for the + // XYZ to display code value transform to be called the forward direction. - // Curves / ParaCurves operates in the range 0.0 to 1.0 as per ICC specifications. + // The ICC spec states that the TRC tags should clamp to [0,1]. For curves + // that are implemented in the ICC profile as LUTs and most parametric + // curves (which become LUTs in OCIO), this is the case. However, as + // floating-point and HDR workflows become more common, the clamping has + // become a critical roadblock. For example, it is now common to have ICC + // profiles for linear color spaces that need to pass values outside [0,1]. + // Therefore, OCIO now implements single entry 'curv' tags and type 0 'para' + // tags without clamping using an ExponentTransform which extends above 1 + // and mirrors below 0. (Note that gamma values of 1 do not need to be + // tested for here since they will be omitted as no-ops later by the + // optimizer.) switch (newDir) { @@ -817,18 +826,12 @@ LocalFileFormat::buildFileOps(OpRcPtrVec & ops, const GammaOpData::Params greenParams = { cachedFile->mGammaRGB[1] }; const GammaOpData::Params blueParams = { cachedFile->mGammaRGB[2] }; const GammaOpData::Params alphaParams = { cachedFile->mGammaRGB[3] }; - auto gamma = std::make_shared(GammaOpData::BASIC_FWD, + auto gamma = std::make_shared(GammaOpData::BASIC_MIRROR_FWD, redParams, greenParams, blueParams, alphaParams); - // GammaOp will clamp at 0 so we don't do it in the RangeOp. - CreateRangeOp(ops, - RangeOpData::EmptyValue(), 1, - RangeOpData::EmptyValue(), 1, - TRANSFORM_DIR_FORWARD); - CreateGammaOp(ops, gamma, TRANSFORM_DIR_FORWARD); } @@ -859,18 +862,13 @@ LocalFileFormat::buildFileOps(OpRcPtrVec & ops, const GammaOpData::Params greenParams = { cachedFile->mGammaRGB[1] }; const GammaOpData::Params blueParams = { cachedFile->mGammaRGB[2] }; const GammaOpData::Params alphaParams = { cachedFile->mGammaRGB[3] }; - auto gamma = std::make_shared(GammaOpData::BASIC_REV, + auto gamma = std::make_shared(GammaOpData::BASIC_MIRROR_REV, redParams, greenParams, blueParams, alphaParams); CreateGammaOp(ops, gamma, TRANSFORM_DIR_FORWARD); - - CreateRangeOp(ops, - RangeOpData::EmptyValue(), 1, - RangeOpData::EmptyValue(), 1, - TRANSFORM_DIR_FORWARD); } break; } diff --git a/src/bindings/python/PyCPUProcessor.cpp b/src/bindings/python/PyCPUProcessor.cpp index 84b91427bf..e5786ba244 100644 --- a/src/bindings/python/PyCPUProcessor.cpp +++ b/src/bindings/python/PyCPUProcessor.cpp @@ -96,7 +96,10 @@ written to the dstImgDesc image, leaving srcImgDesc unchanged. py::buffer_info info = data.request(); checkBufferDivisible(info, 3); - // Interpret as single row of RGB pixels + // --- detect C-contiguous --- + checkCContiguousArray(info); + + // --- proceed normally --- BitDepth bitDepth = getBufferBitDepth(info); py::gil_scoped_release release; @@ -115,6 +118,7 @@ written to the dstImgDesc image, leaving srcImgDesc unchanged. chanStrideBytes, xStrideBytes, yStrideBytes); + self->apply(img); }, "data"_a, @@ -171,6 +175,8 @@ float values is returned, leaving the input list unchanged. py::buffer_info info = data.request(); checkBufferDivisible(info, 4); + // --- detect C-contiguous --- + checkCContiguousArray(info); // Interpret as single row of RGBA pixels BitDepth bitDepth = getBufferBitDepth(info); diff --git a/src/bindings/python/PyUtils.cpp b/src/bindings/python/PyUtils.cpp index 23be750685..fbf1afc31e 100644 --- a/src/bindings/python/PyUtils.cpp +++ b/src/bindings/python/PyUtils.cpp @@ -179,6 +179,27 @@ void checkBufferType(const py::buffer_info & info, BitDepth bitDepth) checkBufferType(info, bitDepthToDtype(bitDepth)); } +void checkCContiguousArray(const py::buffer_info & info) +{ + bool isC = true; + ptrdiff_t itemsize = info.itemsize; + auto shape = info.shape; + auto strides = info.strides; + py::ssize_t ndim = info.ndim; + + ptrdiff_t expected = itemsize; + for (py::ssize_t i = ndim - 1; i >= 0; --i) + { + if (strides[i] != expected) { isC = false; break; } + expected *= shape[i]; + } + + if (!isC) + { + throw std::runtime_error("function only supports C-contiguous (row-major) arrays"); + } +} + void checkBufferDivisible(const py::buffer_info & info, py::ssize_t numChannels) { if (info.size % numChannels != 0) diff --git a/src/bindings/python/PyUtils.h b/src/bindings/python/PyUtils.h index f5d39ee911..8e0ca44ef5 100644 --- a/src/bindings/python/PyUtils.h +++ b/src/bindings/python/PyUtils.h @@ -89,6 +89,9 @@ unsigned long getBufferLut3DGridSize(const py::buffer_info & info); // Throw if vector size is not divisible by channel count void checkVectorDivisible(const std::vector & pixel, size_t numChannels); +// Throw if array is not C-contiguous +void checkCContiguousArray(const py::buffer_info & info); + } // namespace OCIO_NAMESPACE #endif // INCLUDED_OCIO_PYUTILS_H diff --git a/tests/cpu/fileformats/FileFormatICC_tests.cpp b/tests/cpu/fileformats/FileFormatICC_tests.cpp index e0c816093c..d120e24871 100644 --- a/tests/cpu/fileformats/FileFormatICC_tests.cpp +++ b/tests/cpu/fileformats/FileFormatICC_tests.cpp @@ -244,6 +244,8 @@ OCIO_ADD_TEST(FileFormatICC, test_apply) { OCIO::ContextRcPtr context = OCIO::Context::Create(); { + // This test uses a profile where the TRC is a 1024 element LUT. + static const std::string iccFileName("icc-test-3.icm"); OCIO::OpRcPtrVec ops; OCIO_CHECK_NO_THROW(BuildOpsTest(ops, iccFileName, context, OCIO::TRANSFORM_DIR_INVERSE)); @@ -287,7 +289,8 @@ OCIO_ADD_TEST(FileFormatICC, test_apply) op->apply(srcImage, 3); } - // Values outside [0.0, 1.0] are clamped and won't round-trip. + // Currently the LUT-based TRC's clamp the values outside + // [0.0, 1.0], thus those values won't round-trip. static constexpr float bckImage[] = { 0.0f, 0.0f, 0.3f, 0.0f, 0.4f, 0.5f, 0.6f, 0.5f, @@ -301,26 +304,35 @@ OCIO_ADD_TEST(FileFormatICC, test_apply) } { + // This test uses a profile where the TRC is + // a parametric curve of type 0 (basic gamma) with + // gamma values {2.174, 2.174, 2.174, 1.0}. + static const std::string iccFileName("icc-test-2.pf"); OCIO::OpRcPtrVec ops; OCIO_CHECK_NO_THROW(BuildOpsTest(ops, iccFileName, context, OCIO::TRANSFORM_DIR_INVERSE)); OCIO_CHECK_NO_THROW(ops.finalize()); OCIO_CHECK_NO_THROW(ops.optimize(OCIO::OPTIMIZATION_LOSSLESS)); + OCIO_REQUIRE_EQUAL(2, ops.size()); + OCIO_CHECK_EQUAL("", ops[0]->getInfo()); + OCIO_CHECK_EQUAL("", ops[1]->getInfo()); // apply ops - float srcImage[] = { + const std::array srcImage{ -0.1f, 0.0f, 0.3f, 0.0f, 0.4f, 0.5f, 0.6f, 0.5f, 0.7f, 1.0f, 1.9f, 1.0f }; - const float dstImage[] = { - 0.012437f, 0.004702f, 0.070333f, 0.0f, + const std::array dstImage{ + 0.009241f, 0.003003f, 0.070198f, 0.0f, 0.188392f, 0.206965f, 0.343595f, 0.5f, - 0.693246f, 0.863199f, 1.07867f , 1.0f }; + 1.210462f, 1.058761f, 4.003706f, 1.0f }; + + std::array testImage = srcImage; for (const auto & op : ops) { - op->apply(srcImage, 3); + op->apply(testImage.data(), 3); } // Compare results @@ -328,7 +340,7 @@ OCIO_ADD_TEST(FileFormatICC, test_apply) for (unsigned int i = 0; i<12; ++i) { - OCIO_CHECK_CLOSE(srcImage[i], dstImage[i], error); + OCIO_CHECK_CLOSE(testImage[i], dstImage[i], error); } // Invert the processing. @@ -337,24 +349,22 @@ OCIO_ADD_TEST(FileFormatICC, test_apply) OCIO_CHECK_NO_THROW(BuildOpsTest(opsInv, iccFileName, context, OCIO::TRANSFORM_DIR_FORWARD)); OCIO_CHECK_NO_THROW(opsInv.finalize()); OCIO_CHECK_NO_THROW(opsInv.optimize(OCIO::OPTIMIZATION_LOSSLESS)); + OCIO_REQUIRE_EQUAL(2, opsInv.size()); + OCIO_CHECK_EQUAL("", opsInv[0]->getInfo()); + OCIO_CHECK_EQUAL("", opsInv[1]->getInfo()); for (const auto & op : opsInv) { - op->apply(srcImage, 3); + op->apply(testImage.data(), 3); } - // Values outside [0.0, 1.0] are clamped and won't round-trip. - const float bckImage[] = { - 0.0f, 0.0f, 0.3f, 0.0f, - 0.4f, 0.5f, 0.6f, 0.5f, - 0.7f, 1.0f, 1.0f, 1.0f }; - - // Compare results + // For pure-gamma TRCs, values outside [0.0, 1.0] are NOT clamped + // thus those values should round-trip correctly. const float error2 = 2e-4f; for (unsigned int i = 0; i<12; ++i) { - OCIO_CHECK_CLOSE(srcImage[i], bckImage[i], error2); + OCIO_CHECK_CLOSE(testImage[i], srcImage[i], error2); } } diff --git a/tests/python/CPUProcessorTest.py b/tests/python/CPUProcessorTest.py index 4fc5e3c157..128281cfe9 100644 --- a/tests/python/CPUProcessorTest.py +++ b/tests/python/CPUProcessorTest.py @@ -19,7 +19,7 @@ class CPUProcessorTest(unittest.TestCase): - FLOAT_DELTA = 1e+5 + FLOAT_DELTA = 1e-5 UINT_DELTA = 1 @classmethod @@ -385,6 +385,28 @@ def test_apply_rgb_list(self): delta=self.FLOAT_DELTA ) + def test_apply_rgb_buffer_column_major(self): + if not np: + logger.warning("NumPy not found. Skipping test!") + return + + for arr, cpu_proc_fwd in [ + (self.float_rgb_2d, self.default_cpu_proc_fwd), + (self.float_rgb_3d, self.default_cpu_proc_fwd), + (self.half_rgb_2d, self.half_cpu_proc_fwd), + (self.half_rgb_3d, self.half_cpu_proc_fwd), + (self.uint16_rgb_2d, self.uint16_cpu_proc_fwd), + (self.uint16_rgb_3d, self.uint16_cpu_proc_fwd), + (self.uint8_rgb_2d, self.uint8_cpu_proc_fwd), + (self.uint8_rgb_3d, self.uint8_cpu_proc_fwd), + ]: + # Transpose to F-order (column-major) + arr_copy = arr.copy().T + + # Expect runtime error for non-C-contiguous array + with self.assertRaises(RuntimeError): + cpu_proc_fwd.applyRGB(arr_copy) + def test_apply_rgb_buffer(self): if not np: logger.warning("NumPy not found. Skipping test!") @@ -627,3 +649,48 @@ def test_apply_rgba_buffer(self): arr.flat[i], delta=self.UINT_DELTA ) + + def test_apply_rgba_buffer_column_major(self): + if not np: + logger.warning("NumPy not found. Skipping test!") + return + + for arr, cpu_proc_fwd in [ + ( + self.float_rgba_2d, + self.default_cpu_proc_fwd + ), + ( + self.float_rgba_3d, + self.default_cpu_proc_fwd + ), + ( + self.half_rgba_2d, + self.half_cpu_proc_fwd + ), + ( + self.half_rgba_3d, + self.half_cpu_proc_fwd + ), + ( + self.uint16_rgba_2d, + self.uint16_cpu_proc_fwd + ), + ( + self.uint16_rgba_3d, + self.uint16_cpu_proc_fwd + ), + ( + self.uint8_rgba_2d, + self.uint8_cpu_proc_fwd + ), + ( + self.uint8_rgba_3d, + self.uint8_cpu_proc_fwd, + ), + ]: + # Transpose to F-order (column-major) + arr_copy = arr.copy().T + # Expect runtime error for non-C-contiguous array + with self.assertRaises(RuntimeError): + cpu_proc_fwd.applyRGBA(arr_copy)