diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 536ef4915c..386b60c172 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,3 +1,8 @@ + + + +------ :scissors: ------------------------------------------------------------------- + YOU MAY DELETE ALL OF THIS IF YOU ALREADY HAVE A DESCRIPTIVE COMMIT MESSAGE! This is just a template and set of reminders about what constitutes a good PR. diff --git a/.github/workflows/analysis.yml b/.github/workflows/analysis.yml index bdaa0f8c81..aaf69defe6 100644 --- a/.github/workflows/analysis.yml +++ b/.github/workflows/analysis.yml @@ -26,6 +26,7 @@ on: - '!**/scorecard.yml' - '!**/wheel.yml' - '!**.properties' + - '!pyproject.toml' - '!docs/**' # Run analysis on PRs only if the branch name indicates that the purpose of # the PR is related to the Sonar analysis. We don't run on every PR because @@ -51,7 +52,7 @@ jobs: name: "SonarCloud Analysis" # Exclude runs on forks, since only the main org has the SonarCloud # account credentials. - if: github.repository == 'AcademySoftwareFoundation/OpenImageIO' + if: github.event.repository.fork == false uses: ./.github/workflows/build-steps.yml # Must let the called steps workflow inherit necessary secrets secrets: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index eeba96601d..94a95efbf7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,6 +16,7 @@ on: - '!**/scorecard.yml' - '!**/wheel.yml' - '!**.properties' + - '!pyproject.toml' - '!docs/**' pull_request: paths: @@ -26,7 +27,6 @@ on: schedule: # Full nightly build - cron: "0 8 * * *" - if: github.repository == 'AcademySoftwareFoundation/OpenImageIO' workflow_dispatch: # This allows manual triggering of the workflow from the web @@ -41,7 +41,7 @@ concurrency: jobs: aswf-old: - if: ${{ ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'macos-only') }} + if: ${{ (github.event.repository.fork == false || github.event_name != 'schedule') && ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'macos-only') }} name: "(old) ${{matrix.desc}}" uses: ./.github/workflows/build-steps.yml with: @@ -91,7 +91,7 @@ jobs: cxx_std: 17 python_ver: 3.9 simd: "avx2,f16c" - fmt_ver: 8.1.1 + fmt_ver: 9.0.0 opencolorio_ver: v2.3.0 pybind11_ver: v2.9.0 setenvs: export FREETYPE_VERSION=VER-2-12-0 @@ -123,7 +123,7 @@ jobs: vfxyear: 2022 old_node: 1 cxx_std: 17 - fmt_ver: 7.0.1 + fmt_ver: 9.0.0 opencolorio_ver: v2.3.0 openexr_ver: v3.1.0 pybind11_ver: v2.7.0 @@ -145,7 +145,7 @@ jobs: cc_compiler: clang cxx_compiler: clang++ cxx_std: 17 - fmt_ver: 7.0.1 + fmt_ver: 9.0.0 opencolorio_ver: v2.3.0 openexr_ver: v3.1.0 pybind11_ver: v2.7.0 @@ -167,7 +167,7 @@ jobs: vfxyear: 2022 old_node: 1 cxx_std: 17 - fmt_ver: 7.0.1 + fmt_ver: 9.0.0 opencolorio_ver: v2.3.0 openexr_ver: v3.1.0 pybind11_ver: v2.7.0 @@ -190,7 +190,7 @@ jobs: # Linux Tests using ASWF-docker containers # linux-aswf: - if: ${{ ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'macos-only') }} + if: ${{ (github.event.repository.fork == false || github.event_name != 'schedule') && ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'macos-only') }} name: "${{matrix.desc}}" uses: ./.github/workflows/build-steps.yml with: @@ -241,39 +241,6 @@ jobs: pybind11_ver: v2.10.0 setenvs: export PUGIXML_VERSION=v1.13 optional_deps_append: 'LibRaw;Ptex;Qt6' - - desc: VFX2023 icc/C++17 py3.10 exr3.1 ocio2.3 qt5.15 - nametag: linux-vfx2023.icc - runner: ubuntu-latest - container: aswf/ci-osl:2023 - opencolorio_ver: v2.3.0 - python_ver: "3.10" - # simd: "avx2,f16c" - fmt_ver: 7.1.3 - # icc MUST use this older FMT version - pybind11_ver: v2.9.0 - setenvs: export USE_ICC=1 USE_OPENVDB=0 USE_OPENCV=0 - OIIO_EXTRA_CPP_ARGS="-fp-model=precise" - FREETYPE_VERSION=VER-2-13-0 - DISABLE_libuhdr=1 - # For icc, use fp-model precise to eliminate needless LSB errors - # that make test results differ from other platforms. - optional_deps_append: "LibRaw;Ptex;Qt6" - - desc: VFX2025 icx/C++17 py3.11 exr3.3 ocio2.4 qt5.15 - nametag: linux-vfx2023.icx - runner: ubuntu-latest - container: aswf/ci-oiio:2025 - cc_compiler: icx - cxx_compiler: icpx - fmt_ver: 11.2.0 - python_ver: "3.11" - pybind11_ver: v2.13.6 - simd: "avx2,f16c" - benchmark: 1 - setenvs: export USE_OPENVDB=0 USE_OPENCV=0 - UHDR_CMAKE_C_COMPILER=gcc - UHDR_CMAKE_CXX_COMPILER=g++ - # Building libuhdr with icx results in test failures - optional_deps_append: "LibRaw;Ptex;openjph;Qt6" - desc: VFX2024 gcc11/C++17 py3.11 exr3.2 ocio2.3 nametag: linux-vfx2024 runner: ubuntu-latest @@ -316,6 +283,7 @@ jobs: container: aswf/ci-oiio:2025 cxx_std: 17 build_type: Debug + ctest_test_timeout: "240" python_ver: "3.11" simd: "avx2,f16c" fmt_ver: 11.2.0 @@ -335,6 +303,22 @@ jobs: # setenvs: export PUGIXML_VERSION=v1.15 # BUILD_SHARED_LIBS=OFF # optional_deps_append: "openjph;Qt6" + - desc: VFX2025 icx/C++17 py3.11 exr3.3 ocio2.4 qt5.15 + nametag: linux-vfx2025.icx + runner: ubuntu-latest + container: aswf/ci-oiio:2025 + cc_compiler: icx + cxx_compiler: icpx + fmt_ver: 11.2.0 + python_ver: "3.11" + pybind11_ver: v2.13.6 + simd: "avx2,f16c" + benchmark: 1 + setenvs: export USE_OPENVDB=0 USE_OPENCV=0 + UHDR_CMAKE_C_COMPILER=gcc + UHDR_CMAKE_CXX_COMPILER=g++ + # Building libuhdr with icx results in test failures + optional_deps_append: "LibRaw;Ptex;openjph;Qt6" - desc: VFX2026 gcc14/C++20 py3.13 exr3.4 ocio2.4 nametag: linux-vfx2026 runner: ubuntu-latest @@ -387,7 +371,7 @@ jobs: # Linux Tests using GHA Ubuntu runners directly # linux-ubuntu: - if: ${{ ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'macos-only') }} + if: ${{ (github.event.repository.fork == false || github.event_name != 'schedule') && ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'macos-only') }} name: "${{matrix.desc}}" uses: ./.github/workflows/build-steps.yml with: @@ -460,13 +444,13 @@ jobs: simd: avx2,f16c setenvs: export LIBJPEGTURBO_VERSION=3.1.2 LIBPNG_VERSION=v1.6.50 - LIBRAW_VERSION=0.21.4 + LIBRAW_VERSION=0.22.0 LIBTIFF_VERSION=v4.7.1 OPENJPEG_VERSION=v2.5.4 PTEX_VERSION=v2.5.0 PUGIXML_VERSION=v1.15 WEBP_VERSION=v1.6.0 - FREETYPE_VERSION=VER-2-14-0 + FREETYPE_VERSION=VER-2-14-1 USE_OPENVDB=0 # Ensure we are testing all the deps we think we are. We would # like this test to have minimal missing dependencies. @@ -481,7 +465,7 @@ jobs: fmt_ver: master opencolorio_ver: main openexr_ver: main - pybind11_ver: master + pybind11_ver: v3.0.1 python_ver: "3.12" simd: avx2,f16c benchmark: 1 @@ -493,7 +477,7 @@ jobs: PTEX_VERSION=main PUGIXML_VERSION=master WEBP_VERSION=main - OIIO_CMAKE_FLAGS="-DOIIO_HARDENING=2" + OIIO_HARDENING=2 EXTRA_DEP_PACKAGES="python3.12-dev python3-numpy" USE_OPENVDB=0 FREETYPE_VERSION=master @@ -513,10 +497,11 @@ jobs: simd: avx2,f16c setenvs: export OpenImageIO_BUILD_LOCAL_DEPS=all OpenImageIO_DEPENDENCY_BUILD_VERBOSE=ON - LIBRAW_VERSION=0.21.4 + LIBRAW_VERSION=0.22.0 PTEX_VERSION=v2.4.2 PUGIXML_VERSION=v1.14 WEBP_VERSION=v1.4.0 + OIIO_HARDENING=3 - desc: clang18 C++17 avx2 exr3.1 ocio2.3 nametag: linux-clang18 runner: ubuntu-24.04 @@ -543,13 +528,13 @@ jobs: python_ver: "3.12" setenvs: export LIBJPEGTURBO_VERSION=3.1.2 LIBPNG_VERSION=v1.6.50 - LIBRAW_VERSION=0.21.4 + LIBRAW_VERSION=0.22.0 LIBTIFF_VERSION=v4.7.1 OPENJPEG_VERSION=v2.5.4 PTEX_VERSION=v2.4.3 PUGIXML_VERSION=v1.15 WEBP_VERSION=v1.6.0 - FREETYPE_VERSION=VER-2-14-0 + FREETYPE_VERSION=VER-2-14-1 USE_OPENVDB=0 - desc: Linux ARM latest releases clang18 C++20 py3.12 exr3.4 ocio2.4 nametag: linux-arm-latest-releases-clang @@ -564,13 +549,13 @@ jobs: python_ver: "3.12" setenvs: export LIBJPEGTURBO_VERSION=3.1.2 LIBPNG_VERSION=v1.6.50 - LIBRAW_VERSION=0.21.4 + LIBRAW_VERSION=0.22.0 LIBTIFF_VERSION=v4.7.1 OPENJPEG_VERSION=v2.5.4 PTEX_VERSION=v2.4.3 PUGIXML_VERSION=v1.15 WEBP_VERSION=v1.6.0 - FREETYPE_VERSION=VER-2-14-0 + FREETYPE_VERSION=VER-2-14-1 USE_OPENVDB=0 @@ -578,7 +563,7 @@ jobs: # MacOS Tests # macos: - if: ${{ ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'linux-only') }} + if: ${{ (github.event.repository.fork == false || github.event_name != 'schedule') && ! contains(github.ref, 'windows-only') && ! contains(github.ref, 'linux-only') }} name: "${{matrix.desc}}" uses: ./.github/workflows/build-steps.yml with: @@ -627,6 +612,8 @@ jobs: simd: sse4.2,avx2 ctest_test_timeout: 1200 setenvs: export MACOSX_DEPLOYMENT_TARGET=12.0 + INSTALL_QT=0 INSTALL_OPENCV=0 + optional_deps_append: 'OpenCV;Qt5;Qt6' benchmark: 1 - desc: MacOS-14-ARM aclang15/C++20/py3.13 runner: macos-14 @@ -649,7 +636,7 @@ jobs: # Windows Tests # windows: - if: ${{ ! contains(github.ref, 'linux-only') && ! contains(github.ref, 'macos-only') }} + if: ${{ (github.event.repository.fork == false || github.event_name != 'schedule') && ! contains(github.ref, 'linux-only') && ! contains(github.ref, 'macos-only') }} name: "${{matrix.desc}}" uses: ./.github/workflows/build-steps.yml with: @@ -683,7 +670,7 @@ jobs: # built. But we would like to add more dependencies and reduce this list # of exceptions in the future. required_deps: ${{ matrix.required_deps || 'all' }} - optional_deps: ${{ matrix.optional_deps || 'CUDAToolkit;DCMTK;FFmpeg;GIF;JXL;Libheif;LibRaw;Nuke;OpenCV;OpenGL;OpenJPEG;openjph;OpenCV;OpenVDB;Ptex;pystring;Qt5;Qt6;TBB;R3DSDK;${{matrix.optional_deps_append}}' }} + optional_deps: ${{ matrix.optional_deps || 'BZip2;CUDAToolkit;DCMTK;FFmpeg;GIF;JXL;Libheif;LibRaw;Nuke;OpenCV;OpenGL;OpenJPEG;openjph;OpenCV;OpenVDB;Ptex;pystring;Qt5;Qt6;TBB;R3DSDK;${{matrix.optional_deps_append}}' }} strategy: fail-fast: false matrix: @@ -693,13 +680,15 @@ jobs: nametag: windows-2022 vsver: 2022 generator: "Visual Studio 17 2022" - python_ver: "3.9" + python_ver: "3.12" + ctest_test_timeout: "240" setenvs: export OPENIMAGEIO_PYTHON_LOAD_DLLS_FROM_PATH=1 - desc: Windows-2025 VS2022 runner: windows-2025 nametag: windows-2025 vsver: 2022 generator: "Visual Studio 17 2022" - python_ver: "3.9" + python_ver: "3.12" + ctest_test_timeout: "240" setenvs: export OPENIMAGEIO_PYTHON_LOAD_DLLS_FROM_PATH=1 benchmark: 1 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index ffc13b91c6..9d3e3c0da4 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -20,6 +20,7 @@ on: - '**/run.py' - 'src/build-scripts/**' - './*.md' + - 'pyproject.toml' pull_request: paths-ignore: - '**/ci.yml' @@ -33,10 +34,10 @@ on: - '**/run.py' - 'src/build-scripts/**' - './*.md' + - 'pyproject.toml' schedule: # Full nightly build - cron: "0 8 * * *" - if: github.repository == 'AcademySoftwareFoundation/OpenImageIO' workflow_dispatch: # This allows manual triggering of the workflow from the web @@ -51,6 +52,7 @@ concurrency: jobs: docs: name: "Docs / ${{matrix.desc}}" + if: ${{ github.event_name != 'schedule' || github.event.repository.fork == false }} uses: ./.github/workflows/build-steps.yml with: nametag: ${{ matrix.nametag || 'unnamed!' }} diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 41f18cd349..18c775e87d 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -10,7 +10,6 @@ on: push: # Run on pushes to main, but only the official repo, not forks branches: [ "main" ] - if: github.event.pull_request.head.repo.full_name == github.repository pull_request: # Only run on individual PRs if the workflows changed paths: @@ -28,7 +27,7 @@ concurrency: jobs: analysis: name: Scorecards analysis - if: github.repository == 'AcademySoftwareFoundation/OpenImageIO' + if: github.event.repository.fork == false runs-on: ubuntu-latest permissions: # Needed to upload the results to code-scanning dashboard. diff --git a/.github/workflows/wheel.yml b/.github/workflows/wheel.yml index 84f6145a0d..70eaa59e32 100644 --- a/.github/workflows/wheel.yml +++ b/.github/workflows/wheel.yml @@ -59,8 +59,7 @@ jobs: name: Build SDist runs-on: ubuntu-latest if: | - github.event_name != 'schedule' || - github.repository == 'AcademySoftwareFoundation/OpenImageIO' + github.event_name != 'schedule' || github.event.repository.fork == false steps: @@ -86,8 +85,7 @@ jobs: name: Build wheels on Linux runs-on: ubuntu-latest if: | - github.event_name != 'schedule' || - github.repository == 'AcademySoftwareFoundation/OpenImageIO' + ${{ github.event_name != 'schedule' || github.event.repository.fork == false }} strategy: matrix: include: @@ -192,8 +190,7 @@ jobs: name: Build wheels on Linux ARM runs-on: ubuntu-24.04-arm if: | - github.event_name != 'schedule' || - github.repository == 'AcademySoftwareFoundation/OpenImageIO' + github.event_name != 'schedule' || github.event.repository.fork == false strategy: matrix: include: @@ -294,8 +291,7 @@ jobs: name: Build wheels on macOS runs-on: macos-15-intel if: | - github.event_name != 'schedule' || - github.repository == 'AcademySoftwareFoundation/OpenImageIO' + github.event_name != 'schedule' || github.event.repository.fork == false strategy: matrix: include: @@ -383,8 +379,7 @@ jobs: name: Build wheels on macOS ARM runs-on: macos-14 if: | - github.event_name != 'schedule' || - github.repository == 'AcademySoftwareFoundation/OpenImageIO' + github.event_name != 'schedule' || github.event.repository.fork == false strategy: matrix: include: @@ -463,8 +458,7 @@ jobs: name: Build wheels on Windows runs-on: windows-2022 if: | - github.event_name != 'schedule' || - github.repository == 'AcademySoftwareFoundation/OpenImageIO' + github.event_name != 'schedule' || github.event.repository.fork == false strategy: matrix: include: @@ -522,7 +516,7 @@ jobs: runs-on: ubuntu-latest permissions: id-token: write - if: github.event_name == 'push' && (startsWith(github.event.ref, 'refs/tags/v3.0.') || startsWith(github.event.ref, 'refs/tags/v3.1.')) && github.repository == 'AcademySoftwareFoundation/OpenImageIO' + if: github.event_name == 'push' && (startsWith(github.event.ref, 'refs/tags/v3.0.') || startsWith(github.event.ref, 'refs/tags/v3.1.')) && github.event.repository.fork == false steps: - uses: actions/setup-python@f677139bbe7f9c59b41e40162b753c062f5d49a3 # v5.2.0 diff --git a/CHANGES.md b/CHANGES.md index 79d3eb436e..01302429ba 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,37 +2,94 @@ Release 3.2 (target: Sept 2026?) -- compared to 3.1 --------------------------------------------------- ### New minimum dependencies and compatibility changes: + - The deprecated icc compiler is no longer supported. (3.2.0.0) + - **fmt**: Minimum required version is now 9.0 (was 7.0). + ### ⛰️ New features and public API changes: * *New image file format support:* * *oiiotool new features and major improvements*: * *Command line utilities*: + - *iv*: Flip, rotate and save image [#5003](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5003) (by Valery Angelique) (3.2.0.0) * *ImageBuf/ImageBufAlgo*: + - *ImageBuf*: IB::localpixels_as_[writable_]byte_image_span [#5011](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5011) (3.2.0.0, 3.1.10.0) * *ImageCache/TextureSystem*: + - *api/TS*: `IBA::make_texture()` now honors "maketx:threads" hint [#5014](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5014) (3.2.0.0, 3.1.10.0) * New global attribute queries via OIIO::getattribute(): * Miscellaneous API changes: - - *api*: Versioned namespace to preserve ABI compatibility between minor releases [#4869](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4869) (3.2.0.0) + - *api*: Versioned namespace to preserve ABI compatibility between minor releases [#4869](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4869) (3.2.0.0) +* Color management improvements: + - Fix some legacy 'Linear' color references [#4959](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4959) (3.2.0.0) + - Auto convert between oiio:ColorSpace and CICP attributes in I/O [#4964](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4964) (by Brecht Van Lommel) (3.0.14.0, 3.2.0.0) + - *openexr*: Write OpenEXR colorInteropID metadata based on oiio:ColorSpace [#4967](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4967) (by Brecht Van Lommel) (3.0.14.0, 3.2.0.0) + - *jpeg-xl*: CICP read and write support for JPEG-XL [#4968](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4968) (by Brecht Van Lommel) (3.2.0.0, 3.1.9.0) + - *jpeg-xl*: ICC read and write for JPEG-XL files (issue 4649) [#4905](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4905) (by shanesmith-dwa) (3.0.14.0, 3.2.0.0) +* Other notable new feature: + - *heif*: Add IOProxy support for both input and output [#5017](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5017) (by Brecht Van Lommel) (3.2.0.0, 3.1.10.0) + ### 🚀 Performance improvements + - *perf*: `ImageBufAlgo::resample` and `oiiotool --resample` improvements to speed up 20x or more [#4993](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4993) (3.2.0.0, 3.1.10.0) + ### 🐛 Fixes and feature enhancements - - *ffmpeg*: 10 bit video had wrong green channel [#4935](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4935) (by Brecht Van Lommel) (3.1.7.0, 3.2.0.0) - - *iff*: Handle non-zero origin, protect against buffer overflows [#4925](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4925) (3.1.7.0, 3.2.0.0) + - *IBA*: IBA::compare_Yee() accessed the wrong channel [#4976](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4976) (by Pavan Madduri) (3.2.0.0) + - *exif*: Support EXIF 3.0 tags [#4961](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4961) (3.2.0.0) + - *imagebuf*: Fix set_pixels bug, didn't consider roi = All [#4949](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4949) (3.2.0.0) + - *ffmpeg*: 10 bit video had wrong green channel [#4935](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4935) (by Brecht Van Lommel) (3.2.0.0, 3.1.7.0) + - *heif*: Add IOProxy support for both input and output [#5017](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5017) (by Brecht Van Lommel) (3.2.0.0, 3.1.10.0) + - *heif*: Fix: Could not output AVIF when libheif has no HEVC support [#5013](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5013) (by Brecht Van Lommel) (3.2.0.0, 3.1.10.0) + - *heif*: Fix error saving multiple images with different bit depths [#5018](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5018) (by Brecht Van Lommel) (3.2.0.0, 3.1.10.0) + - *iff*: Handle non-zero origin, protect against buffer overflows [#4925](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4925) (3.2.0.0, 3.1.7.0) + - *jpeg*: Fix wrong pointers/crashing when decoding CMYK jpeg files [#4963](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4963) (3.2.0.0) + - *jpeg-2000*: Type warning in assertion in jpeg2000output.cpp [#4952](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4952) (3.2.0.0) - *jpeg-xl*: ICC read and write for JPEG-XL files (issue 4649) [#4905](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4905) (by shanesmith-dwa) (3.2.0.0) - - *jpeg-xl*: Correctly set Quality for JPEG XL [#4933](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4933) (3.1.7.0, 3.2.0.0) - - *openexr*: Support for idManifest and deepImageState (experimental) [#4877](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4877) (3.1.7.0, 3.2.0.0) - - *openexr*: ACES Container hint for exr outputs [#4907](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4907) (by Oktay Comu) (3.1.7.0, 3.2.0.0) + - *jpeg-xl*: Correctly set Quality for JPEG XL [#4933](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4933) (3.2.0.0, 3.1.7.0) + - *jpeg-xl*: CICP read and write support for JPEG XL [#4968](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4968) (by Brecht Van Lommel) (3.2.0.0, 3.1.9.0) + - *openexr*: Support for idManifest and deepImageState (experimental) [#4877](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4877) (3.2.0.0, 3.1.7.0) + - *openexr*: ACES Container hint for exr outputs [#4907](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4907) (by Oktay Comu) (3.2.0.0, 3.1.7.0) + - *openexr*: Write OpenEXR colorInteropID metadata based on oiio:ColorSpace [#4967](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4967) (by Brecht Van Lommel) (3.0.14.0, 3.2.0.0) + - *openexr*: Improve attribute translation rules [#4946](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4946) (3.2.0.0) + - *openexr*: ACES container writes colorInteropId instead of colorInteropID [#4966](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4966) (by Brecht Van Lommel) (3.2.0.0) + - *png*: We were not correctly suppressing hint metadata [#4983](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4983) (3.2.0.0) + - *sgi*: Implement RLE encoding support for output [#4990](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4990) (by Jesse Yurkovich) (3.2.0.0) + - *webp*: Allow out-of-order scanlines when writing webp [#4973](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4973) (by Pavan Madduri) (3.2.0.0) + - *webp*: Use correct resolution limits for WebpOutput::open [#5016](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5016) (by Jesse Yurkovich) (3.2.0.0, 3.1.10.0) + - *webp*: Fix missing oiio:UnassociatedAlpha on input [#5020](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5020) (by Brecht Van Lommel) (3.2.0.0, 3.1.10.0) ### 🔧 Internals and developer goodies + - *fix*: Several bug fixes related to internal use of image_span [#5004](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5004) (3.2.0.0, 3.1.10.0) + - *filesystem.h*: Speedup to detect the existence of files on Windows [#4977](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4977) (by JacksonSun-adsk) (3.2.0.0) ### 🏗 Build/test/CI and platform ports * OIIO's CMake build system and scripts: - - *build*: Allow auto-build of just required packages by setting `OpenImageIO_BUILD_MISSING_DEPS` to `required`. [#4927](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4927) (3.1.7.0, 3.2.0.0) - - *build*: Make dependency report more clear about what was required [#4929](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4929) (3.1.7.0, 3.2.0.0) + - *build*: Allow auto-build of just required packages by setting `OpenImageIO_BUILD_MISSING_DEPS` to `required`. [#4927](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4927) (3.2.0.0, 3.1.7.0) + - *build*: Make dependency report more clear about what was required [#4929](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4929) (3.2.0.0, 3.1.7.0) + - *build*: Fix HARDENING build options [#4996](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4996) (3.2.0.0) + - *build*: Fully disable tests when their required dependencies are missing [#5005](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5005) (3.2.0.0, 3.1.10.0) * Dependency and platform support: - - *build/deps*: Additional auto-build capabilities for dependencies that are not found: GIF library [#4921](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4921) (by Valery Angelique), OpenJPEG [#4911](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4911) (by Danny Greenstein) (3.1.7.0, 3.2.0.0) + - *deps*: Additional auto-build capabilities for dependencies that are not found: GIF library [#4921](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4921) (by Valery Angelique), OpenJPEG [#4911](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4911) (by Danny Greenstein) (3.2.0.0, 3.1.7.0) + - *deps*: Disable LERC in libTIFF local build script [#4957](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4957) (by LI JI) (3.2.0.0, 3.1.8.0) + - *deps*: Test against libraw 0.21.5 [#4988](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4988) (3.2.0.0, 3.1.9.0) + - *build/platforms*: Fix building on OpenBSD [#5001](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5001) (by Brad Smith) (3.2.0.0, 3.1.10.0) + - *build/deps*: Bump OCIO auto-build ver to 2.5.1 [#5022](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5022) (by Zach Lewis) (3.2.0.0, 3.1.10.0) + - *build/deps*: Use libheif exported config if available [#5012](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5012) (3.2.0.0, 3.1.10.0) + - *build/deps*: Libheif 1.21 support [#4992](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4992) (3.2.0.0, 3.1.10.0) * Testing and Continuous integration (CI) systems: - - *ci*: Python wheel building improvements: use ccache [#4924](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4924) (by Larry Gritz), unbreak wheel release + other enhancements pt 1 [#4937](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4937) (by Zach Lewis) (3.1.7.0, 3.2.0.0) - - *ci*: Simplify ci workflow by using build-steps for old aswf containers, too [#4932](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4932) (3.1.7.0, 3.2.0.0) - - *ci*: We were not correctly setting fmt version from job options [#4939](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4939) (3.1.7.0, 3.2.0.0) + - *tests*: Image_span_test reduce benchmark load for debug and CI renders [#4951](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4951) (3.2.0.0, 3.1.8.0) + - *tests*: Add new ref image for jpeg test [#5007](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5007) (3.2.0.0, 3.1.10.0) + - *ci*: Python wheel building improvements: use ccache [#4924](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4924) (by Larry Gritz), unbreak wheel release + other enhancements pt 1 [#4937](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4937) (by Zach Lewis) (3.2.0.0, 3.1.7.0) + - *ci*: Simplify ci workflow by using build-steps for old aswf containers, too [#4932](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4932) (3.2.0.0, 3.1.7.0) + - *ci*: We were not correctly setting fmt version from job options [#4939](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4939) (3.2.0.0, 3.1.7.0) + - *ci*: Emergency fix change deprecated sonarqube action [#4969](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4969) (3.2.0.0) + - *ci*: Try python 3.13 to fix Mac breakage on CI [#4970](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4970) (3.2.0.0) + - *ci*: Freetype adjustments [#4999](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4999) (3.2.0.0) + - *ci*: Speed up macos15 intel variant by not installing Qt [#4998](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4998) (3.2.0.0, 3.1.10.0) + - *ci*: Don't run non-wheel workflows when only pyproject.toml changes [#4997](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4997) (3.2.0.0, 3.1.10.0) + - *ci*: Windows runners switched which python version they had [#5010](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5010) (3.2.0.0, 3.1.10.0) + - *ci*: Test against libraw 0.22 for 'latest' test variants [#5009](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5009) (3.2.0.0, 3.1.10.0) + - *ci*: Lock bleeding edge to pybind11 latest version [#5024](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5024) (3.2.0.0, 3.1.10.0) ### 📚 Notable documentation changes - - *docs*: Update/correct explanation of "openexr:core" attribute, and typo fixes [#4943](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4943) (3.1.7.0, 3.2.0.0) + - *docs*: Update/correct explanation of "openexr:core" attribute, and typo fixes [#4943](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4943) (3.2.0.0, 3.1.7.0) + - *docs*: Remove outdated/wrong description in INSTALL.md [#5008](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5008) (3.2.0.0) ### 🏢 Project Administration + - *admin*: Minor rewording in the issue and PR templates [#4982](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4982) (3.2.0.0) + - *admin*: Refine PR template to give more visual separation [#4995](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4995) (3.2.0.0) ### 🤝 Contributors --- @@ -40,7 +97,67 @@ Release 3.2 (target: Sept 2026?) -- compared to 3.1 -Release 3.1.7.0 (Nov 1, 2025) -- compared to 3.1.7.0 +Release 3.1.10.0 (Feb 1, 2026) -- compared to 3.1.9.0 +----------------------------------------------------- + - *perf*: `IBA::resample()` and `oiiotool --resample` improvements to speed up 20x or more [#4993](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4993) + - *ImageBuf*: IB::localpixels_as_[writable_]byte_image_span [#5011](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5011) + - ImageBufAlgo*: IBA::make_texture now honors "maketx:threads" hint [#5014](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5014) + - *heif*: Add IOProxy for input and output [#5017](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5017) (by Brecht Van Lommel) + - *heif*: Can not output AVIF when libheif has no HEVC support [#5013](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5013) (by Brecht Van Lommel) + - *heif*: Error saving multiple images with different bit depths [#5018](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5018) (by Brecht Van Lommel) + - *webp*: Use correct resolution limits for WebpOutput::open [#5016](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5016) (by Jesse Yurkovich) + - *webp*: Missing oiio:UnassociatedAlpha on input [#5020](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5020) (by Brecht Van Lommel) + - *fix*: Several bug fixes related to internal use of image_span [#5004](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5004) + - *build*: Fix building on OpenBSD [#5001](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5001) (by Brad Smith) + - *deps*: Libheif 1.21 support [#4992](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4992) + - *deps*: Bump OCIO auto-build ver to 2.5.1 [#5022](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5022) (by Zach Lewis) + - *deps*: Use libheif exported config if available [#5012](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5012) + - *tests*: Add new ref image for jpeg test [#5007](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5007) + - *tests*: Fully disable tests when their required dependencies are missing [#5005](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5005) + - *ci*: Speed up macos15 intel variant by not installing Qt [#4998](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4998) + - *ci*: Don't run non-wheel workflows when only pyproject.toml changes [#4997](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4997) + - *ci*: Windows runners switched which python version they had [#5010](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5010) + - *ci*: Test against libraw 0.22 for 'latest' test variants [#5009](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5009) + - *ci*: Lock bleeding edge to pybind11 latest version [#5024](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5024) + + +Release 3.1.9.0 (Jan 1, 2026) -- compared to 3.1.8.0 +---------------------------------------------------- + - Color management improvements: + - Auto convert between oiio:ColorSpace and CICP attributes in I/O [#4964](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4964) (by Brecht Van Lommel) + - *exr*: Write OpenEXR colorInteropID metadata based on oiio:ColorSpace [#4967](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4967) (by Brecht Van Lommel) + - *jpeg-xl*: CICP read and write support for JPEG-XL [#4968](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4968) (by Brecht Van Lommel) + - *jpeg-xl*: ICC read and write for JPEG-XL files (issue 4649) [#4905](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4905) (by shanesmith-dwa) + - *png*: We were not correctly suppressing hint metadata [#4983](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4983) + - *sgi*: Implement RLE encoding support for output [#4990](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4990) (by Jesse Yurkovich) + - *webp*: Allow out-of-order scanlines when writing webp [#4973](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4973) (by Pavan Madduri) + - *fix/IBA*: IBA::compare_Yee() accessed the wrong channel [#4976](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4976) (by Pavan Madduri) + - *perf/filesystem.h*: Speedup to detect the existence of files on Windows [#4977](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4977) (by JacksonSun-adsk) + - *ci*: Address tight disk space on GHA runners [#4974](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4974) + - *ci*: Optimize install_homebrew_deps by coalescing installs [#4975](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4975) + - *ci*: Build_Ptex.bash should build Ptex using C++17 [#4978](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4978) + - *ci*: Unbreak CI by adjusting Ubuntu installs [#4981](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4981) + - *ci*: Test against libraw 0.21.5 [#4988](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4988) + - *docs*: Fix missing docs for `OIIO:attribute()` and `OIIO::getattribute()` [#4987](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4987) + + +Release 3.1.8.0 (Dec 1, 2025) -- compared to 3.1.7.0 +---------------------------------------------------- + - *exif*: Support EXIF 3.0 tags [#4961](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4961) + - *jpeg*: Fix wrong pointers/crashing when decoding CMYK jpeg files [#4963](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4963) + - *openexr*: Improve attribute translation rules [#4946](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4946) + - *openexr*: ACES container writes colorInteropId instead of colorInteropID [#4966](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4966) (by Brecht Van Lommel) + - *color mgmt*: Fix some legacy 'Linear' color references [#4959](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4959) + - *imagebuf*: Fix `ImageBuf::set_pixels()` bug, didn't consider roi = All [#4949](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4949) + - *tests*: Image_span_test reduce benchmark load for debug and CI renders [#4951](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4951) + - *build*: Type warning in assertion in jpeg2000output.cpp [#4952](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4952) + - *build*: Disable LERC in libTIFF local build script [#4957](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4957) (by LI JI) + - *ci*: Fix broken ci, debug and static cases, bump some latest [#4954](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4954) + - *ci*: Unbreak icc/icx CI [#4958](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4958) + - *admin*: Update some license notices [#4955](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4955) + + +Release 3.1.7.0 (Nov 1, 2025) -- compared to 3.1.6.1 ---------------------------------------------------- - *openexr*: Support for idManifest and deepImageState (experimental) [#4877](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4877) (3.1.7.0) - *openexr*: ACES Container hint for exr outputs [#4907](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4907) (by Oktay Comu) (3.1.7.0) @@ -67,7 +184,7 @@ Release 3.1.6.2 (Oct 3, 2025) -- compared to 3.1.6.1 - *oiioversion.h*: Restore definition of `OIIO_NAMESPACE_USING` macro [#4920](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4920) -Release 3.1 (Oct 2 2025) -- compared to 3.0.x +Release 3.1 (Oct 2, 2025) -- compared to 3.0.x ----------------------------------------------------- - Beta 1: Aug 22, 2025 - Beta 2: Sep 19, 2025 @@ -383,6 +500,31 @@ asterisk) had not previously contributed to the project. --- +Release 3.0.15.0 (Feb 1, 2026) -- compared to 3.0.14.0 +------------------------------------------------------- + - *heif*: Can not output AVIF when libheif has no HEVC support [#5013](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5013) (by Brecht Van Lommel) + - *heif*: Error saving multiple images with different bit depths [#5018](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5018) (by Brecht Van Lommel) + - *webp*: Use correct resolution limits for WebpOutput::open [#5016](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5016) (by Jesse Yurkovich) + - *ci*: Speed up macos15 intel variant by not installing Qt [#4998](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4998) + - *ci*: Windows runners switched which python version they had [#5010](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5010) + - *ci*: Lock bleeding edge to pybind11 latest version [#5024](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/5024) + + +Release 3.0.14.0 (Jan 1, 2026) -- compared to 3.0.13.0 +------------------------------------------------------- + - *fix(IBA)*: IBA::compare_Yee() accessed the wrong channel [#4976](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4976) (by Pavan Madduri) + - *ci*: Test against libraw 0.21.5 [#4988](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4988) + - *ci*: Address tight disk space on GHA runners [#4974](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4974) + + +Release 3.0.13.0 (Dec 1, 2025) -- compared to 3.0.12.0 +------------------------------------------------------- + - *exif*: Support EXIF 3.0 tags [#4961](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4961) + - *build*: Disable LERC in libTIFF local build script [#4957](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4957) (by LI JI) + - *ci*: Fix broken ci, debug and static cases, bump some latest [#4954](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4954) + - *ci*: Unbreak icc/icx CI [#4958](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4958) + + Release 3.0.12.0 (Nov 1, 2025) -- compared to 3.0.11.0 ------------------------------------------------------- - *iff*: Handle non-zero origin, protect against buffer overflows [#4925](https://github.com/AcademySoftwareFoundation/OpenImageIO/pull/4925) diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ed1589cfc..62229859ce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -112,6 +112,7 @@ else () endif () option (${PROJ_NAME}_BUILD_TOOLS "Build the command-line tools" ON) option (${PROJ_NAME}_BUILD_TESTS "Build the unit tests" ON) +option (OIIO_USE_HWY "Enable experimental Google Highway SIMD optimizations (if Highway is available)" OFF) set (OIIO_LIBNAME_SUFFIX "" CACHE STRING "Optional name appended to ${PROJECT_NAME} libraries that are built") option (BUILD_OIIOUTIL_ONLY "If ON, will build *only* libOpenImageIO_Util" OFF) diff --git a/CREDITS.md b/CREDITS.md index f3044fd4bf..ed1bb4c15b 100644 --- a/CREDITS.md +++ b/CREDITS.md @@ -110,6 +110,7 @@ lg@openimageio.org * Imarz * Irena Damsky * Ismael Cortes +* Jackson Sun * Jan Hettenkofer * Jan Honsbrok * Jens Lindgren @@ -191,6 +192,7 @@ lg@openimageio.org * Paul Franz * Paul Melis * Paul Molodowitch +* Pavan Madduri * Pavel Karneliuk * Pete Larabell * Peter Horvath @@ -223,6 +225,7 @@ lg@openimageio.org * Sergio Rojas * Shane Ambler * Shane Smith +* Shashvat K. Singh * Simon Boorer * Solomon Boulos * SRHMorris diff --git a/INSTALL.md b/INSTALL.md index 68d69aa67e..8d662290f1 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -17,23 +17,21 @@ NEW or CHANGED MINIMUM dependencies since the last major release are **bold**. * C++17 or higher (also builds with C++20 and C++23) * The default build mode is C++17. This can be controlled by via the CMake configuration flag: `-DCMAKE_CXX_STANDARD=20`, etc. - * Compilers: gcc 9.3 - 14.2, **clang 10** - 20, MSVS 2017 - 2022 (v19.14 - and up), Intel icc 19+, Intel OneAPI C++ compiler 2022+. + * Compilers: gcc 9.3 - 14.2, clang 10 - 20, MSVS 2017 - 2022 (v19.14 + and up), Intel OneAPI C++ compiler 2022+. * CMake >= 3.18.2 (tested through 4.1) * Imath >= 3.1 (tested through 3.2 and main) * OpenEXR >= 3.1 (tested through 3.4 and main) * libTIFF >= 4.0 (tested through 4.7 and master) - * *OpenColorIO >= 2.3* (tested through 2.5 and main) + * OpenColorIO >= 2.3 (tested through 2.5 and main) * libjpeg >= 8 (tested through jpeg9e), or libjpeg-turbo >= 2.1 (tested through 3.1) * zlib >= 1.2.7 (tested through 1.3.1) - * [fmtlib](https://github.com/fmtlib/fmt) >= 7.0 (tested through 12.0 and master). - If not found at build time, this will be automatically downloaded unless - the build sets `-DBUILD_MISSING_FMT=OFF`. + * **[fmtlib](https://github.com/fmtlib/fmt) >= 9.0** (tested through 12.1 and master). + If not found at build time, this will be automatically downloaded and built. * [Robin-map](https://github.com/Tessil/robin-map) (unknown minimum, tested through 1.4, which is the recommended version). If not found at build time, - this will be automatically downloaded unless the build sets - `-DBUILD_MISSING_FMT=OFF`. + this will be automatically downloaded and built. ### Optional dependencies -- features may be disabled if not found * If you are building the `iv` viewer (which will be disabled if any of @@ -41,13 +39,13 @@ NEW or CHANGED MINIMUM dependencies since the last major release are **bold**. * Qt5 >= 5.6 (tested through 5.15) or Qt6 (tested through 6.9) * OpenGL * If you are building the Python bindings or running the testsuite: - * **Python >= 3.9** (tested through 3.13). + * Python >= 3.9 (tested through 3.13). * pybind11 >= 2.7 (tested through 3.0) * NumPy (tested through 2.2.4) * If you want support for PNG files: * libPNG >= 1.6.0 (tested though 1.6.50) * If you want support for camera "RAW" formats: - * LibRaw >= 0.20 (tested though 0.21.4 and master) + * LibRaw >= 0.20 (tested though 0.22.0 and master) * If you want support for a wide variety of video formats: * ffmpeg >= 4.0 (tested through 8.0) * If you want support for jpeg 2000 images: @@ -63,8 +61,8 @@ NEW or CHANGED MINIMUM dependencies since the last major release are **bold**. * If you want support for GIF images: * giflib >= 5.0 (tested through 5.2.2) * If you want support for HEIF/HEIC or AVIF images: - * libheif >= 1.11 (1.16 required for correct orientation support, - tested through 1.20.2) + * libheif >= 1.11 (1.16 required for correct orientation support and + 1.17 required for monochrome HEIC support; tested through 1.21.1) * libheif must be built with an AV1 encoder/decoder for AVIF support. * If you want support for DICOM medical image files: * DCMTK >= 3.6.1 (tested through 3.6.9) diff --git a/Makefile b/Makefile index 830e39a208..115a56eb68 100644 --- a/Makefile +++ b/Makefile @@ -293,7 +293,7 @@ test: build @ ${CMAKE} -E cmake_echo_color --switch=$(COLOR) --cyan "Running tests ${TEST_FLAGS}..." @ ( cd ${build_dir} ; \ PYTHONPATH=${PWD}/${build_dir}/lib/python/site-packages \ - ctest -E broken ${TEST_FLAGS} \ + ctest ${TEST_FLAGS} \ ) @ ( if [[ "${CODECOV}" == "1" ]] ; then \ cd ${build_dir} ; \ diff --git a/docs/dev/Architecture.md b/docs/dev/Architecture.md index 5e52bf4143..72f81d8907 100644 --- a/docs/dev/Architecture.md +++ b/docs/dev/Architecture.md @@ -117,6 +117,10 @@ objects. These algorithms include simple operations like copying, resizing, and compositing images, as well as more complex operations like color conversions, resizing, filtering, etc. +Some performance-critical `ImageBufAlgo` implementations have SIMD-accelerated +paths using Google Highway. For implementation details and guidance for adding +new kernels, see `docs/dev/ImageBufAlgo_Highway.md`. + ## Image caching: TextureSystem and ImageCache There are situations where ImageBuf is still not the right abstraction, diff --git a/docs/dev/ImageBufAlgo_Highway.md b/docs/dev/ImageBufAlgo_Highway.md new file mode 100644 index 0000000000..960766978c --- /dev/null +++ b/docs/dev/ImageBufAlgo_Highway.md @@ -0,0 +1,264 @@ +ImageBufAlgo Highway (hwy) Implementation Guide +============================================== + +This document explains how OpenImageIO uses Google Highway (hwy) to accelerate +selected `ImageBufAlgo` operations, and how to add or modify kernels in a way +that preserves OIIO semantics while keeping the code maintainable. + +This is a developer-facing document about the implementation structure in +`src/libOpenImageIO/`. It does not describe the public API behavior of the +algorithms. + + +Goals and non-goals +------------------- + +Goals: +- Make the hwy-backed code paths easy to read and easy to extend. +- Centralize repetitive boilerplate (type conversion, tails, ROI pointer math). +- Preserve OIIO's numeric semantics (normalized integer model). +- Keep scalar fallbacks as the source of truth for tricky layout cases. + +Non-goals: +- Explain Highway itself. Refer to the upstream Highway documentation. +- Guarantee that every ImageBufAlgo op has a hwy implementation. + + +Where the code lives +-------------------- + +Core helpers: +- `src/libOpenImageIO/imagebufalgo_hwy_pvt.h` + +Typical hwy call sites: +- `src/libOpenImageIO/imagebufalgo_addsub.cpp` +- `src/libOpenImageIO/imagebufalgo_muldiv.cpp` +- `src/libOpenImageIO/imagebufalgo_mad.cpp` +- `src/libOpenImageIO/imagebufalgo_pixelmath.cpp` +- `src/libOpenImageIO/imagebufalgo_xform.cpp` (some ops are hwy-accelerated) + + +Enabling and gating the hwy path +------------------------------- + +The hwy path is only used when: +- Highway usage is enabled at runtime (`OIIO::pvt::enable_hwy`). +- The relevant `ImageBuf` objects have local pixel storage (`localpixels()` is + non-null), meaning the data is in process memory rather than accessed through + an `ImageCache` tile abstraction. +- The operation can be safely expressed as contiguous streams of pixels/channels + for the hot path, or the code falls back to a scalar implementation for + strided/non-contiguous layouts. + +The common gating pattern looks like: +- In a typed `*_impl` dispatcher: check `OIIO::pvt::enable_hwy` and `localpixels` + and then call a `*_impl_hwy` function; otherwise call `*_impl_scalar`. + +Important: the hwy path is an optimization. Correctness must not depend on hwy. + + +OIIO numeric semantics: why we promote to float +---------------------------------------------- + +OIIO treats integer image pixels as normalized values: +- Unsigned integers represent [0, 1]. +- Signed integers represent approximately [-1, 1] with clamping for INT_MIN. + +Therefore, most pixel math must be performed in float (or double) space, even +when the stored data is integer. This is why the hwy layer uses the +"LoadPromote/Operate/DemoteStore" pattern. + +For additional discussion (and pitfalls of saturating integer arithmetic), see: +- `HIGHWAY_SATURATING_ANALYSIS.md` + + +The core pattern: LoadPromote -> RunHwy* -> DemoteStore +------------------------------------------------------- + +The helper header `imagebufalgo_hwy_pvt.h` defines the reusable building blocks: + +1) Computation type selection + - `SimdMathType` selects `float` for most types, and `double` only when + the destination type is `double`. + + Rationale: + - Float math is significantly faster on many targets. + - For OIIO, integer images are normalized to [0,1] (or ~[-1,1]), so float + precision is sufficient for typical image processing workloads. + +2) Load and promote (with normalization) + - `LoadPromote(d, ptr)` and `LoadPromoteN(d, ptr, count)` load values and + normalize integer ranges into the computation space. + + Rationale: + - Consolidates all normalization and conversion logic in one place. + - Prevents subtle drift where each operation re-implements integer scaling. + - Ensures tail handling ("N" variants) is correct and consistent. + +3) Demote and store (with denormalization/clamp/round) + - `DemoteStore(d, ptr, v)` and `DemoteStoreN(d, ptr, v, count)` reverse the + normalization and store results in the destination pixel type. + + Rationale: + - Centralizes rounding and clamping behavior for all destination types. + - Ensures output matches OIIO scalar semantics. + +4) Generic kernel runners (streaming arrays) + - `RunHwyUnaryCmd`, `RunHwyCmd` (binary), `RunHwyTernaryCmd` + - These are the primary entry points for most hwy kernels. + + Rationale: + - Encapsulates lane iteration and tail processing once. + - The call sites only provide the per-lane math lambda, not the boilerplate. + + +Native integer runners: when they are valid +------------------------------------------- + +Some operations are "scale-invariant" under OIIO's normalized integer model. +For example, for unsigned integer add: +- `(a/max + b/max)` in float space, then clamped to [0,1], then scaled by max + matches saturated integer add `SaturatedAdd(a, b)` for the same bit depth. + +For those cases, `imagebufalgo_hwy_pvt.h` provides: +- `RunHwyUnaryNativeInt` +- `RunHwyBinaryNativeInt` + +These should only be used when all of the following are true: +- The operation is known to be scale-invariant under the normalization model. +- Input and output types are the same integral type. +- The operation does not depend on mixed types or float-range behavior. + +Rationale: +- Avoids promotion/demotion overhead and can be materially faster. +- Must be opt-in and explicit, because many operations are NOT compatible with + raw integer arithmetic (e.g. multiplication, division, pow). + + +Local pixel pointer helpers: reducing boilerplate safely +------------------------------------------------------- + +Most hwy call sites need repeated pointer and stride computations: +- Pixel size in bytes. +- Scanline size in bytes. +- Base pointer to local pixels. +- Per-row pointer for a given ROI and scanline. +- Per-pixel pointer for non-contiguous fallbacks. + +To centralize that, `imagebufalgo_hwy_pvt.h` defines: +- `HwyPixels(ImageBuf&)` and `HwyPixels(const ImageBuf&)` + returning a small view (`HwyLocalPixelsView`) with: + - base pointer (`std::byte*` / `const std::byte*`) + - `pixel_bytes`, `scanline_bytes` + - `xbegin`, `ybegin`, `nchannels` +- `RoiNChannels(roi)` for `roi.chend - roi.chbegin` +- `ChannelsContiguous(view, nchannels)`: + true only when the pixel stride exactly equals `nchannels * sizeof(T)` +- `PixelBase(view, x, y)`, `ChannelPtr(view, x, y, ch)` +- `RoiRowPtr(view, y, roi)` for the start of the ROI row at `roi.xbegin` and + `roi.chbegin`. + +Rationale: +- Avoids duplicating fragile byte-offset math across many ops. +- Makes it visually obvious what the code is doing: "get row pointer" vs + "compute offset by hand." +- Makes non-contiguous fallback paths less error-prone by reusing the same + pointer computations. + +Important: these helpers are only valid for `ImageBuf` instances with local +pixels (`localpixels()` non-null). The call sites must check that before using +them. + + +Contiguous fast path vs non-contiguous fallback +----------------------------------------------- + +Most operations implement two paths: + +1) Contiguous fast path: + - Used when pixels are tightly packed for the ROI's channel range. + - The operation is executed as a 1D stream of length: + `roi.width() * (roi.chend - roi.chbegin)` + - Uses `RunHwy*Cmd` (or native-int runner) and benefits from: + - fewer branches + - fewer pointer computations + - auto tail handling + +2) Non-contiguous fallback: + - Used when pixels have padding, unusual strides, or channel subsets that do + not form a dense stream. + - Typically loops pixel-by-pixel and channel-by-channel. + - May still use the `ChannelPtr` helpers to compute correct addresses. + +Rationale: +- The contiguous path is where SIMD delivers large gains. +- Trying to SIMD-optimize arbitrary strided layouts often increases complexity + and risk for marginal benefit. Keeping a scalar fallback preserves + correctness and maintainability. + + +How to add a new hwy kernel +--------------------------- + +Step 1: Choose the kernel shape +- Unary: `R = f(A)` -> use `RunHwyUnaryCmd` +- Binary: `R = f(A, B)` -> use `RunHwyCmd` +- Ternary: `R = f(A, B, C)` -> use `RunHwyTernaryCmd` + +Step 2: Decide if a native-int fast path is valid +- Only for scale-invariant ops and same-type integral inputs/outputs. +- Use `RunHwyUnaryNativeInt` / `RunHwyBinaryNativeInt` when safe. +- Otherwise, always use the promote/demote runners. + +Step 3: Implement the hwy body with a contig check +Typical structure inside `*_impl_hwy`: +- Acquire views once: + - `auto Rv = HwyPixels(R);` + - `auto Av = HwyPixels(A);` etc. +- In the parallel callback: + - compute `nchannels = RoiNChannels(roi)` + - compute `contig = ChannelsContiguous<...>(...)` for each image + - for each scanline y: + - `Rtype* r_row = RoiRowPtr(Rv, y, roi);` + - `const Atype* a_row = RoiRowPtr(Av, y, roi);` etc. + - if contig: call `RunHwy*` with `n = roi.width() * nchannels` + - else: fall back per pixel, per channel + +Step 4: Keep the scalar path as the reference +- The scalar implementation should remain correct for all layouts and types. +- The hwy path should match scalar results for supported cases. + + +Design rationale summary +------------------------ + +This design intentionally separates concerns: +- Type conversion and normalization are centralized (`LoadPromote`, + `DemoteStore`). +- SIMD lane iteration and tail handling are centralized (`RunHwy*` runners). +- Image address computations are centralized (`HwyPixels`, `RoiRowPtr`, + `ChannelPtr`). +- Operation-specific code is reduced to short lambdas expressing the math. + +This makes the hwy layer: +- Easier to maintain: fewer places to fix bugs when semantics change. +- Easier to extend: adding an op mostly means writing the math lambda and the + dispatch glue. +- Safer: correctness for unusual layouts remains in scalar fallbacks. + + +Notes on `half` +--------------- + +The hwy conversion helpers handle `half` by converting through +`hwy::float16_t`. This currently assumes the underlying `half` representation +is compatible with how Highway loads/stores 16-bit floats. + +If this assumption is revisited in the future, it should be changed as a +separate, explicit correctness/performance project. + + + + + + diff --git a/pyproject.toml b/pyproject.toml index 29e7ede18d..04f176cbd4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -131,23 +131,28 @@ WebP_BUILD_VERSION = "1.5.0" [tool.cibuildwheel.windows.environment] SKBUILD_CMAKE_BUILD_TYPE = "MinSizeRel" -[[tool.cibuildwheel.overrides]] -# Trigger the build & validation of the python stubs for certain platforms. -# The test command acts as a kind of "post-build" callback where it's possible -# for the stub-generator to import the freshly-built wheel. -# There are two entry-points which are designed to call generate_stubs.py through -# this test command: -# - `make pystubs` is called during local development to generate the -# stubs and copy them into the git repo to be committed and reviewed. -# - in CI, the cibuildwheel action is used to validate that the stubs match what -# has been committed to the repo. -test-requires = "mypy~=1.15.0 stubgenlib~=0.1.0" -# Note: the python version here must be kept in sync with src/python/stubs/CMakeLists.txt -select = "cp311-manylinux_*64" -inherit.test-command = "append" -test-command = [ - "python {project}/src/python/stubs/generate_stubs.py --out-path '/output' --validate-path '{project}/src/python/stubs/OpenImageIO/__init__.pyi'", -] +# Temporarily disabled test below. The CI stub generation seems broken, and +# is failing CI every time. Just turn off this check until we can figure out +# why it is broken. +# ---- +# [[tool.cibuildwheel.overrides]] +# # Trigger the build & validation of the python stubs for certain platforms. +# # The test command acts as a kind of "post-build" callback where it's possible +# # for the stub-generator to import the freshly-built wheel. +# # There are two entry-points which are designed to call generate_stubs.py through +# # this test command: +# # - `make pystubs` is called during local development to generate the +# # stubs and copy them into the git repo to be committed and reviewed. +# # - in CI, the cibuildwheel action is used to validate that the stubs match what +# # has been committed to the repo. +# test-requires = "mypy~=1.15.0 stubgenlib~=0.1.0" +# # Note: the python version here must be kept in sync with src/python/stubs/CMakeLists.txt +# select = "cp311-manylinux_*64" +# inherit.test-command = "append" +# test-command = [ +# "python {project}/src/python/stubs/generate_stubs.py --out-path '/output' --validate-path '{project}/src/python/stubs/OpenImageIO/__init__.pyi'", +# ] +# ---- [tool.mypy] files = [ diff --git a/src/bmp.imageio/bmpinput.cpp b/src/bmp.imageio/bmpinput.cpp index a39d0b2061..ba43d4fc5f 100644 --- a/src/bmp.imageio/bmpinput.cpp +++ b/src/bmp.imageio/bmpinput.cpp @@ -259,6 +259,13 @@ BmpInput::open(const std::string& name, ImageSpec& newspec, case WINDOWS_V5: m_spec.attribute("bmp:version", 5); break; } + if (m_dib_header.cpalete && !m_colortable.size()) { + errorfmt( + "BMP error: bad BPP ({}) for palette image -- presumed corrupt file", + m_dib_header.bpp); + return false; + } + // Default presumption is that a BMP file is meant to look reasonable on a // display, so assume it's sRGB. This is not really correct -- see the // comments below. @@ -391,8 +398,9 @@ BmpInput::read_native_scanline(int subimage, int miplevel, int y, int /*z*/, size_t scanline_bytes = m_spec.scanline_bytes(); uint8_t* mscanline = (uint8_t*)data; - if (m_dib_header.compression == RLE4_COMPRESSION - || m_dib_header.compression == RLE8_COMPRESSION) { + if ((m_dib_header.compression == RLE4_COMPRESSION + || m_dib_header.compression == RLE8_COMPRESSION) + && m_colortable.size()) { for (int x = 0; x < m_spec.width; ++x) { int p = m_uncompressed[(m_spec.height - 1 - y) * m_spec.width + x]; auto& c = colortable(p); diff --git a/src/bmp.imageio/bmpoutput.cpp b/src/bmp.imageio/bmpoutput.cpp index c6e772799c..e60ae820ff 100644 --- a/src/bmp.imageio/bmpoutput.cpp +++ b/src/bmp.imageio/bmpoutput.cpp @@ -27,9 +27,6 @@ class BmpOutput final : public ImageOutput { bool close(void) override; bool write_scanline(int y, int z, TypeDesc format, const void* data, stride_t xstride) override; - bool write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, - stride_t zstride) override; private: int64_t m_padded_scanline_size; @@ -38,7 +35,6 @@ class BmpOutput final : public ImageOutput { bmp_pvt::DibInformationHeader m_dib_header; int64_t m_image_start; unsigned int m_dither; - std::vector m_tilebuffer; std::vector m_scratch; std::vector m_buf; // more tmp space for write_scanline @@ -110,11 +106,6 @@ BmpOutput::open(const std::string& name, const ImageSpec& spec, OpenMode mode) m_image_start = iotell(); - // If user asked for tiles -- which this format doesn't support, emulate - // it by buffering the whole image. - if (m_spec.tile_width && m_spec.tile_height) - m_tilebuffer.resize(m_spec.image_bytes()); - return true; } @@ -157,23 +148,6 @@ BmpOutput::write_scanline(int y, int z, TypeDesc format, const void* data, } - -bool -BmpOutput::write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, stride_t zstride) -{ - if (!ioproxy_opened()) { - errorfmt("write_tile called but file is not open."); - return false; - } - - // Emulate tiles by buffering the whole image - return copy_tile_to_image_buffer(x, y, z, format, data, xstride, ystride, - zstride, m_tilebuffer.data()); -} - - - bool BmpOutput::close(void) { @@ -182,17 +156,8 @@ BmpOutput::close(void) return true; } - bool ok = true; - if (m_spec.tile_width && m_tilebuffer.size()) { - // Handle tile emulation -- output the buffered pixels - OIIO_DASSERT(m_tilebuffer.size()); - ok &= write_scanlines(m_spec.y, m_spec.y + m_spec.height, 0, - m_spec.format, m_tilebuffer.data()); - std::vector().swap(m_tilebuffer); - } - init(); - return ok; + return true; } diff --git a/src/build-scripts/build_Freetype.bash b/src/build-scripts/build_Freetype.bash index 41c74f5c20..cd27c00ddd 100755 --- a/src/build-scripts/build_Freetype.bash +++ b/src/build-scripts/build_Freetype.bash @@ -11,7 +11,7 @@ set -ex # Repo and branch/tag/commit of Freetype to download if we don't have it yet FREETYPE_REPO=${FREETYPE_REPO:=https://github.com/freetype/freetype.git} -FREETYPE_VERSION=${FREETYPE_VERSION:=VER-2-13-3} +FREETYPE_VERSION=${FREETYPE_VERSION:=VER-2-14-1} # Where to put Freetype repo source (default to the ext area) LOCAL_DEPS_DIR=${LOCAL_DEPS_DIR:=${PWD}/ext} diff --git a/src/build-scripts/build_libraw.bash b/src/build-scripts/build_libraw.bash index b5114d3aab..2b4b3bef92 100755 --- a/src/build-scripts/build_libraw.bash +++ b/src/build-scripts/build_libraw.bash @@ -11,7 +11,7 @@ set -ex # Which LibRaw to retrieve, how to build it LIBRAW_REPO=${LIBRAW_REPO:=https://github.com/LibRaw/LibRaw.git} -LIBRAW_VERSION=${LIBRAW_VERSION:=0.21.4} +LIBRAW_VERSION=${LIBRAW_VERSION:=0.21.5} # Where to install the final results LOCAL_DEPS_DIR=${LOCAL_DEPS_DIR:=${PWD}/ext} diff --git a/src/build-scripts/ci-benchmark.bash b/src/build-scripts/ci-benchmark.bash index 7d5a5f0a33..d3b3551b1b 100755 --- a/src/build-scripts/ci-benchmark.bash +++ b/src/build-scripts/ci-benchmark.bash @@ -13,13 +13,14 @@ ls build ls $BUILD_BIN_DIR mkdir -p build/benchmarks -for t in image_span_test ; do +for t in image_span_test span_test ; do echo echo echo "$t" echo "========================================================" - ${BUILD_BIN_DIR}/$t > build/benchmarks/$t.out - cat build/benchmarks/$t.out + OpenImageIO_CI=0 ${BUILD_BIN_DIR}/$t | tee build/benchmarks/$t.out + # Note: set OpenImageIO_CI=0 to avoid CI-specific automatic reduction of + # the number of trials and iterations. echo "========================================================" echo "========================================================" echo diff --git a/src/build-scripts/gh-installdeps.bash b/src/build-scripts/gh-installdeps.bash index 0dabcf61e0..5ce3e0afee 100755 --- a/src/build-scripts/gh-installdeps.bash +++ b/src/build-scripts/gh-installdeps.bash @@ -69,14 +69,7 @@ if [[ "$ASWF_ORG" != "" ]] ; then time pip3 install ${PIP_INSTALLS} || true fi - if [[ "$CXX" == "icpc" || "$CC" == "icc" || "$USE_ICC" != "" ]] ; then - # Lock down icc to 2022.1 because newer versions hosted on the Intel - # repo require a glibc too new for the ASWF CentOS7-based containers - # we run CI on. - sudo cp src/build-scripts/oneAPI.repo /etc/yum.repos.d - sudo /usr/bin/yum install -y intel-oneapi-compiler-dpcpp-cpp-and-cpp-classic-2022.1.0.x86_64 - set +e; source /opt/intel/oneapi/setvars.sh --config oneapi_2022.1.0.cfg; set -e - elif [[ "$CXX" == "icpc" || "$CC" == "icc" || "$USE_ICC" != "" || "$CXX" == "icpx" || "$CC" == "icx" || "$USE_ICX" != "" ]] ; then + if [[ "$CXX" == "icpx" || "$CC" == "icx" || "$USE_ICX" != "" ]] ; then sudo cp src/build-scripts/oneAPI.repo /etc/yum.repos.d sudo yum install -y intel-oneapi-compiler-dpcpp-cpp-and-cpp-classic # If we needed to lock down to a particular version, we could: diff --git a/src/build-scripts/gh-win-installdeps.bash b/src/build-scripts/gh-win-installdeps.bash index d4f0283853..48ad421924 100755 --- a/src/build-scripts/gh-win-installdeps.bash +++ b/src/build-scripts/gh-win-installdeps.bash @@ -32,6 +32,10 @@ elif [[ "$PYTHON_VERSION" == "3.9" ]] ; then export CMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH;/c/hostedtoolcache/windows/Python/3.9.13/x64" export Python_EXECUTABLE="/c/hostedtoolcache/windows/Python/3.9.13/x64/python3.exe" export PYTHONPATH=$OpenImageIO_ROOT/lib/python${PYTHON_VERSION}/site-packages +elif [[ "$PYTHON_VERSION" == "3.12" ]] ; then + export CMAKE_PREFIX_PATH="$CMAKE_PREFIX_PATH;/c/hostedtoolcache/windows/Python/3.12.10/x64" + export Python_EXECUTABLE="/c/hostedtoolcache/windows/Python/3.12.10/x64/python3.exe" + export PYTHONPATH=$OpenImageIO_ROOT/lib/python${PYTHON_VERSION}/site-packages fi pip install numpy diff --git a/src/build-scripts/install_homebrew_deps.bash b/src/build-scripts/install_homebrew_deps.bash index 62a433e614..e8cfe2bc59 100755 --- a/src/build-scripts/install_homebrew_deps.bash +++ b/src/build-scripts/install_homebrew_deps.bash @@ -47,7 +47,7 @@ if [[ "$OIIO_BREW_INSTALL_PACKAGES" == "" ]] ; then robin-map \ tbb \ " - if [[ "${USE_OPENCV}" != "0" ]] && [[ "${INSTALL_OPENCV:=1}" != "0" ]] ; then + if [[ "${USE_OPENCV:=}" != "0" ]] && [[ "${INSTALL_OPENCV:=1}" != "0" ]] ; then OIIO_BREW_INSTALL_PACKAGES+=" opencv" fi if [[ "${USE_QT:=1}" != "0" ]] && [[ "${INSTALL_QT:=1}" != "0" ]] ; then diff --git a/src/cmake/build_Freetype.cmake b/src/cmake/build_Freetype.cmake index cd9becae46..a38e17151a 100644 --- a/src/cmake/build_Freetype.cmake +++ b/src/cmake/build_Freetype.cmake @@ -6,10 +6,10 @@ # Freetype by hand! ###################################################################### -set_cache (Freetype_BUILD_VERSION 2.13.2 "Freetype version for local builds") +set_cache (Freetype_BUILD_VERSION 2.14.1 "Freetype version for local builds") set (Freetype_GIT_REPOSITORY "https://github.com/freetype/freetype") -set (Freetype_GIT_TAG "VER-2-13-2") -set_cache (Freetype_BUILD_SHARED_LIBS OFF +set (Freetype_GIT_TAG "VER-2-14-1") +set_cache (Freetype_BUILD_SHARED_LIBS ${LOCAL_BUILD_SHARED_LIBS_DEFAULT} DOC "Should a local Freetype build, if necessary, build shared libraries" ADVANCED) # We would prefer to build a static Freetype, but haven't figured out how to make # it all work with the static dependencies, it just makes things complicated diff --git a/src/cmake/build_OpenColorIO.cmake b/src/cmake/build_OpenColorIO.cmake index 8502992696..ea68c003c6 100644 --- a/src/cmake/build_OpenColorIO.cmake +++ b/src/cmake/build_OpenColorIO.cmake @@ -6,7 +6,7 @@ # OpenColorIO by hand! ###################################################################### -set_cache (OpenColorIO_BUILD_VERSION 2.4.2 "OpenColorIO version for local builds") +set_cache (OpenColorIO_BUILD_VERSION 2.5.1 "OpenColorIO version for local builds") set (OpenColorIO_GIT_REPOSITORY "https://github.com/AcademySoftwareFoundation/OpenColorIO") set (OpenColorIO_GIT_TAG "v${OpenColorIO_BUILD_VERSION}") set_cache (OpenColorIO_BUILD_SHARED_LIBS OFF diff --git a/src/cmake/build_fmt.cmake b/src/cmake/build_fmt.cmake index 95a497c70a..2b41502e55 100644 --- a/src/cmake/build_fmt.cmake +++ b/src/cmake/build_fmt.cmake @@ -6,7 +6,7 @@ # fmt by hand! ###################################################################### -set_cache (fmt_BUILD_VERSION 10.2.1 "fmt version for local builds") +set_cache (fmt_BUILD_VERSION 12.1.0 "fmt version for local builds") set (fmt_GIT_REPOSITORY "https://github.com/fmtlib/fmt") set (fmt_GIT_TAG "${fmt_BUILD_VERSION}") # Note: fmt doesn't put "v" in front of version for its git tags @@ -22,8 +22,6 @@ build_dependency_with_cmake(fmt -D FMT_TEST=OFF ) -# Set some things up that we'll need for a subsequent find_package to work -set (fmt_ROOT ${fmt_INSTALL_DIR}) - # Signal to caller that we need to find again at the installed location set (fmt_REFIND TRUE) +set (fmt_VERSION ${fmt_BUILD_VERSION}) diff --git a/src/cmake/build_pybind11.cmake b/src/cmake/build_pybind11.cmake index e7a6f3e282..b6c827f764 100644 --- a/src/cmake/build_pybind11.cmake +++ b/src/cmake/build_pybind11.cmake @@ -8,9 +8,12 @@ set_cache (pybind11_BUILD_VERSION 3.0.1 "pybind11 version for local builds") set (pybind11_GIT_REPOSITORY "https://github.com/pybind/pybind11") -set (pybind11_GIT_TAG "v${pybind11_BUILD_VERSION}") +set_cache (pybind11_GIT_TAG "v${pybind11_BUILD_VERSION}" + "pybind11 git tag to checkout") +set_cache (pybind11_GIT_COMMIT "" + "pybind11 specific commit to checkout (overrides tag if set)") set_cache (pybind11_BUILD_SHARED_LIBS ${LOCAL_BUILD_SHARED_LIBS_DEFAULT} - DOC "Should a local pybind11 build, if necessary, build shared libraries" ADVANCED) + "Should a local pybind11 build, if necessary, build shared libraries" ADVANCED) string (MAKE_C_IDENTIFIER ${pybind11_BUILD_VERSION} pybind11_VERSION_IDENT) diff --git a/src/cmake/compiler.cmake b/src/cmake/compiler.cmake index da4e76da48..6936ac51b9 100644 --- a/src/cmake/compiler.cmake +++ b/src/cmake/compiler.cmake @@ -485,30 +485,34 @@ endif () # recommended default for optimized, shipping code. # 2 : enable features that trade off performance for security, recommended # for debugging or deploying in security-sensitive environments. -# 3 : enable features that have a significant performance impact, only -# recommended for debugging. +# 3 : enable features that have a significant performance impact, to maximize +# finding bugs without regard to performance. Only recommended for +# debugging. # # Some documentation: # https://best.openssf.org/Compiler-Hardening-Guides/Compiler-Options-Hardening-Guide-for-C-and-C++.html # https://www.gnu.org/software/libc/manual/html_node/Source-Fortification.html # https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_macros.html # https://libcxx.llvm.org/Hardening.html -# +# https://www.productive-cpp.com/hardening-cpp-programs-stack-protector/ +# https://medium.com/@simontoth/daily-bit-e-of-c-hardened-mode-of-standard-library-implementations-18be2422c372 +# https://cheatsheetseries.owasp.org/cheatsheets/C-Based_Toolchain_Hardening_Cheat_Sheet.html + if (${CMAKE_BUILD_TYPE} STREQUAL "Debug") - set (${PROJ_NAME}_HARDENING_DEFAULT 3) + set (${PROJ_NAME}_HARDENING_DEFAULT 2) # Extensive else () - set (${PROJ_NAME}_HARDENING_DEFAULT 1) + set (${PROJ_NAME}_HARDENING_DEFAULT 1) # Fast endif () set_cache (${PROJ_NAME}_HARDENING ${${PROJ_NAME}_HARDENING_DEFAULT} - "Turn on security hardening features 0, 1, 2, 3") + "Turn on security hardening features 0=none, 1=fast, 2=extensive, 3=debug") # Implementation: -if (HARDENING GREATER_EQUAL 1) +add_compile_definitions (${PROJ_NAME}_HARDENING_DEFAULT=${${PROJ_NAME}_HARDENING}) +if (${PROJ_NAME}_HARDENING GREATER_EQUAL 1) + # Enable PIE and pie to build as position-independent executables and + # libraries, needed for address space randomization used by some kernels. + set (CMAKE_POSITION_INDEPENDENT_CODE ON) # Features that should not detectably affect performance if (COMPILER_IS_GCC_OR_ANY_CLANG) - # Enable PIE and pie to build as position-independent executables, - # needed for address space randomiztion used by some kernels. - add_compile_options (-fPIE -pie) - add_link_options (-fPIE -pie) # Protect against stack overwrites. Is allegedly not a performance # tradeoff. add_compile_options (-fstack-protector-strong) @@ -516,21 +520,39 @@ if (HARDENING GREATER_EQUAL 1) endif () # Defining _FORTIFY_SOURCE provides buffer overflow checks in modern gcc & # clang with some compiler-assisted deduction of buffer lengths) for the - # many C functions such as memcpy, strcpy, sprintf, etc. See: - add_compile_definitions (_FORTIFY_SOURCE=${${PROJ_NAME}_HARDENING}) + # many C functions such as memcpy, strcpy, sprintf, etc. But it requires + # optimization, so we don't do it for debug builds. + if ((CMAKE_COMPILER_IS_CLANG OR (GCC_VERSION VERSION_GREATER_EQUAL 14)) + AND NOT CMAKE_BUILD_TYPE STREQUAL "Debug") + add_compile_definitions (_FORTIFY_SOURCE=${${PROJ_NAME}_HARDENING}) + endif () +endif () +if (${PROJ_NAME}_HARDENING EQUAL 1) # Setting _LIBCPP_HARDENING_MODE enables various hardening features in # clang/llvm's libc++ 18.0 and later. - add_compiler_definitions (_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST) -endif () -if (HARDENING GREATER_EQUAL 2) + if (OIIO_CLANG_VERSION VERSION_GREATER_EQUAL 18.0 OR OIIO_APPLE_CLANG_VERSION VERSION_GREATER_EQUAL 18.0) + add_compile_definitions (_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_FAST) + endif () +elseif (${PROJ_NAME}_HARDENING EQUAL 2) # Features that might impact performance measurably - add_compile_definitions (_GLIBCXX_ASSERTIONS) - add_compiler_definitions (_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE) -endif () -if (HARDENING GREATER_EQUAL 3) + if (GCC_VERSION VERSION_GREATER_EQUAL 14) + # I've had trouble turning this on in older gcc + add_compile_definitions (_GLIBCXX_ASSERTIONS) + endif () + if (OIIO_CLANG_VERSION VERSION_GREATER_EQUAL 18.0 OR OIIO_APPLE_CLANG_VERSION VERSION_GREATER_EQUAL 18.0) + add_compile_definitions (_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE) + endif () +elseif (${PROJ_NAME}_HARDENING EQUAL 3) # Debugging features that might impact performance significantly - add_compile_definitions (_GLIBCXX_DEBUG) - add_compiler_definitions (_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG) + if (GCC_VERSION VERSION_GREATER_EQUAL 14) + # I've had trouble turning this on in older gcc + add_compile_definitions (_GLIBCXX_ASSERTIONS) + # N.B. _GLIBCXX_DEBUG changes ABI, so don't do this: + # add_compile_definitions (_GLIBCXX_DEBUG) + endif () + if (OIIO_CLANG_VERSION VERSION_GREATER_EQUAL 18.0 OR OIIO_APPLE_CLANG_VERSION VERSION_GREATER_EQUAL 18.0) + add_compile_definitions (_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG) + endif () endif () diff --git a/src/cmake/dependency_utils.cmake b/src/cmake/dependency_utils.cmake index 25a1172555..c6468663c1 100644 --- a/src/cmake/dependency_utils.cmake +++ b/src/cmake/dependency_utils.cmake @@ -609,7 +609,7 @@ macro (build_dependency_with_cmake pkgname) # noValueKeywords: "NOINSTALL" # singleValueKeywords: - "GIT_REPOSITORY;GIT_TAG;GIT_COMMIT;VERSION;SOURCE_SUBDIR;GIT_SHALLOW;QUIET" + "GIT_REPOSITORY;GIT_TAG;GIT_COMMIT;VERSION;SOURCE_SUBDIR;QUIET" # multiValueKeywords: "CMAKE_ARGS" # argsToParse: @@ -629,8 +629,10 @@ macro (build_dependency_with_cmake pkgname) unset (${pkgname}_GIT_CLONE_ARGS) unset (_pkg_exec_quiet) - if (_pkg_GIT_SHALLOW OR "${_pkg_GIT_SHALLOW}" STREQUAL "") - list (APPEND ${pkgname}_GIT_CLONE_ARGS --depth 1) + if (NOT "${pkg_GIT_TAG}" STREQUAL "" AND "${_pkg_GIT_COMMIT}" STREQUAL "") + # If a tag was specified, but not a specific commit, do a shallow + # clone. + list (APPEND ${pkgname}_GIT_CLONE_ARGS -b ${pkg_GIT_TAG} --depth 1) endif () if (_pkg_QUIET OR "${_pkg_QUIET}" STREQUAL "") list (APPEND ${pkgname}_GIT_CLONE_ARGS -q ERROR_VARIABLE ${pkgname}_clone_errors) @@ -641,12 +643,10 @@ macro (build_dependency_with_cmake pkgname) find_package (Git REQUIRED) if (NOT IS_DIRECTORY ${${pkgname}_LOCAL_SOURCE_DIR}) message (STATUS "COMMAND ${GIT_EXECUTABLE} clone ${_pkg_GIT_REPOSITORY} " - "-b ${_pkg_GIT_TAG} " "${${pkgname}_LOCAL_SOURCE_DIR} " "${${pkgname}_GIT_CLONE_ARGS} " "${_pkg_exec_quiet}") execute_process(COMMAND ${GIT_EXECUTABLE} clone ${_pkg_GIT_REPOSITORY} - -b ${_pkg_GIT_TAG} ${${pkgname}_LOCAL_SOURCE_DIR} ${${pkgname}_GIT_CLONE_ARGS} ${_pkg_exec_quiet}) diff --git a/src/cmake/externalpackages.cmake b/src/cmake/externalpackages.cmake index 12467ae6b6..623bc1ca5c 100644 --- a/src/cmake/externalpackages.cmake +++ b/src/cmake/externalpackages.cmake @@ -163,8 +163,9 @@ checked_find_package (GIF VERSION_MIN 5.0) # For HEIF/HEIC/AVIF formats checked_find_package (Libheif VERSION_MIN 1.11 + PREFER_CONFIG RECOMMEND_MIN 1.16 - RECOMMEND_MIN_REASON "for orientation support") + RECOMMEND_MIN_REASON "1.16 for orientation support, 1.17 for monochrome support") checked_find_package (LibRaw VERSION_MIN 0.20.0 @@ -200,11 +201,9 @@ checked_find_package (R3DSDK NO_RECORD_NOTFOUND) # RED camera set (NUKE_VERSION "7.0" CACHE STRING "Nuke version to target") checked_find_package (Nuke NO_RECORD_NOTFOUND) -if (FFmpeg_FOUND OR FREETYPE_FOUND) +if ((FFmpeg_FOUND OR FREETYPE_FOUND OR TARGET Freetype::Freetype) + AND NOT TARGET BZip2::BZip2) checked_find_package (BZip2) # Used by ffmpeg and freetype - if (NOT BZIP2_FOUND) - set (BZIP2_LIBRARIES "") # TODO: why does it break without this? - endif () endif() @@ -225,6 +224,11 @@ if (USE_QT AND OPENGL_FOUND) endif () +# Google Highway for SIMD (optional optimization) +if (OIIO_USE_HWY) + checked_find_package (hwy) +endif () + # Tessil/robin-map checked_find_package (Robinmap REQUIRED VERSION_MIN 1.2.0 @@ -232,9 +236,9 @@ checked_find_package (Robinmap REQUIRED ) # fmtlib -option (OIIO_INTERNALIZE_FMT "Copy fmt headers into /include/OpenImageIO/detail/fmt" ON) +set_option (OIIO_INTERNALIZE_FMT "Copy fmt headers into /include/OpenImageIO/detail/fmt" ON) checked_find_package (fmt REQUIRED - VERSION_MIN 7.0 + VERSION_MIN 9.0 BUILD_LOCAL missing ) get_target_property(FMT_INCLUDE_DIR fmt::fmt-header-only INTERFACE_INCLUDE_DIRECTORIES) diff --git a/src/cmake/modules/FindLibheif.cmake b/src/cmake/modules/FindLibheif.cmake index 5d061af6ea..61c93feac7 100644 --- a/src/cmake/modules/FindLibheif.cmake +++ b/src/cmake/modules/FindLibheif.cmake @@ -27,7 +27,7 @@ find_library (LIBHEIF_LIBRARY heif HINTS ${LIBHEIF_LIBRARY_PATH} ENV LIBHEIF_LIBRARY_PATH - DOC "The directory where libheif libraries reside") + DOC "The found libheif library") if (LIBHEIF_INCLUDE_DIR) file(STRINGS "${LIBHEIF_INCLUDE_DIR}/libheif/heif_version.h" TMP REGEX "^#define LIBHEIF_VERSION[ \t].*$") @@ -44,11 +44,11 @@ if (Libheif_FOUND) set(LIBHEIF_INCLUDES "${LIBHEIF_INCLUDE_DIR}") set(LIBHEIF_LIBRARIES "${LIBHEIF_LIBRARY}") - if (NOT TARGET Libheif::Libheif) - add_library(Libheif::Libheif UNKNOWN IMPORTED) - set_target_properties(Libheif::Libheif PROPERTIES + if (NOT TARGET heif) + add_library(heif UNKNOWN IMPORTED) + set_target_properties(heif PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${LIBHEIF_INCLUDES}") - set_property(TARGET Libheif::Libheif APPEND PROPERTY + set_property(TARGET heif APPEND PROPERTY IMPORTED_LOCATION "${LIBHEIF_LIBRARIES}") endif () endif() diff --git a/src/cmake/testing.cmake b/src/cmake/testing.cmake index 8bd975efbc..bfa588ca7b 100644 --- a/src/cmake/testing.cmake +++ b/src/cmake/testing.cmake @@ -47,7 +47,7 @@ set(OIIO_TESTSUITE_IMAGEDIR "${PROJECT_BINARY_DIR}/testsuite" CACHE PATH # # The optional SUFFIX is appended to the test name. # -# The optinonal ENVIRONMENT is a list of environment variables to set for the +# The optional ENVIRONMENT is a list of environment variables to set for the # test. # macro (oiio_add_tests) @@ -56,9 +56,12 @@ macro (oiio_add_tests) set (_ats_testdir "${OIIO_TESTSUITE_IMAGEDIR}/${_ats_IMAGEDIR}") # If there was a FOUNDVAR param specified and that variable name is # not defined, mark the test as broken. + set (_test_disabled FALSE) + set (_test_notfound FALSE) foreach (_var ${_ats_FOUNDVAR}) if (NOT ${_var}) set (_ats_LABEL "broken") + set (_test_notfound TRUE) endif () endforeach () set (_test_disabled 0) @@ -66,7 +69,7 @@ macro (oiio_add_tests) if ((NOT "${${_var}}" STREQUAL "" AND NOT "${${_var}}") OR (NOT "$ENV{${_var}}" STREQUAL "" AND NOT "$ENV{${_var}}")) set (_ats_LABEL "broken") - set (_test_disabled 1) + set (_test_disabled TRUE) endif () endforeach () # For OCIO 2.2+, have the testsuite use the default built-in config @@ -74,6 +77,8 @@ macro (oiio_add_tests) "OIIO_TESTSUITE_OCIOCONFIG=ocio://default") if (_test_disabled) message (STATUS "Skipping test(s) ${_ats_UNPARSED_ARGUMENTS} because of disabled ${_ats_ENABLEVAR}") + elseif (_test_notfound) + message (STATUS "Skipping test(s) ${_ats_UNPARSED_ARGUMENTS} because of missing dependency from ${_ats_FOUNDVAR}") elseif (_ats_IMAGEDIR AND NOT EXISTS ${_ats_testdir}) # If the directory containing reference data (images) for the test # isn't found, point the user at the URL. @@ -110,6 +115,7 @@ macro (oiio_add_tests) "OIIO_TESTSUITE_ROOT=${_testsuite}" "OIIO_TESTSUITE_SRC=${_testsrcdir}" "OIIO_TESTSUITE_CUR=${_testdir}" + "Python_EXECUTABLE=${Python3_EXECUTABLE}" ${_ats_ENVIRONMENT}) if (NOT ${_ats_testdir} STREQUAL "") set_property(TEST ${_testname} APPEND PROPERTY ENVIRONMENT diff --git a/src/doc/builtinplugins.rst b/src/doc/builtinplugins.rst index f6eea37efd..92c8a5f6bf 100644 --- a/src/doc/builtinplugins.rst +++ b/src/doc/builtinplugins.rst @@ -806,6 +806,10 @@ attributes are supported: having Orientation 1). If zero, then libheif will not reorient the image and the Orientation metadata will be set to reflect the camera orientation. + * - ``oiio:ioproxy`` + - ptr + - Pointer to a ``Filesystem::IOProxy`` that will handle the I/O, for + example by reading from memory rather than the file system. **Configuration settings for HEIF output** @@ -824,11 +828,22 @@ control aspects of the writing itself: - If supplied, can be ``"heic"`` or ``"avif"``, but may optionally have a quality value appended, like ``"heic:90"``. Quality can be 1-100, with 100 meaning lossless. The default is 75. + * - ``oiio:ioproxy`` + - ptr + - Pointer to a ``Filesystem::IOProxy`` that will handle the I/O, for + example by writing to memory rather than the file system. | +**Additional notes and limitations** + +* The underlying libheif dependency must be 1.16 or newer to support the + "oiio:reorient" configuration option and the "heif:Orientation" metadata. +* The underlying libheif dependency must be 1.17 or newer to support + monochrome HEIC images. + .. _sec-bundledplugins-ico: ICO diff --git a/src/doc/imagebufalgo.rst b/src/doc/imagebufalgo.rst index b013ce0d20..200417accc 100644 --- a/src/doc/imagebufalgo.rst +++ b/src/doc/imagebufalgo.rst @@ -152,6 +152,68 @@ the computation without spawning additional threads, which might tend to crowd out the other application threads. +SIMD Performance and Data Types +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Many ImageBufAlgo operations use SIMD (Single Instruction, Multiple Data) +optimizations powered by the Google Highway library to achieve significant +performance improvements, particularly for integer image formats. + +**Integer Type Optimizations:** + +OpenImageIO treats all integer images as normalized Standard Dynamic Range +(SDR) data: + +* Unsigned integers (``uint8``, ``uint16``, ``uint32``, ``uint64``) are + normalized to the [0.0, 1.0] range: ``float_value = int_value / max_value`` +* Signed integers (``int8``, ``int16``, ``int32``, ``int64``) are normalized + to approximately the [-1.0, 1.0] range: ``float_value = int_value / max_value`` + +Most ImageBufAlgo operations convert integer data to float, perform the +operation, and convert back. Highway SIMD provides 3-5x speedup for these +operations compared to scalar code. + +**Scale-Invariant Operations:** + +Certain operations are *scale-invariant*, meaning they produce identical +results whether performed on raw integers or normalized floats. For these +operations, OpenImageIO uses native integer SIMD paths that avoid float +conversion entirely, achieving 6-12x speedup (2-3x faster than the float +promotion path): + +* ``add``, ``sub`` (with saturation) +* ``min``, ``max`` +* ``abs``, ``absdiff`` + +These optimizations automatically activate when all input and output images +have matching integer types (e.g., all ``uint8``). When types differ or when +mixing integer and float images, the standard float promotion path is used. + +**Controlling SIMD Optimizations:** + +Highway SIMD is enabled by default. To disable it globally:: + + OIIO::attribute("enable_hwy", 0); + +Or via environment variable:: + + export OPENIMAGEIO_ENABLE_HWY=0 + +This is primarily useful for debugging or performance comparison. In normal +use, the optimizations should remain enabled for best performance. + +**Performance Expectations:** + +Typical speedups with Highway SIMD (compared to scalar code): + +* Float operations: 3-5x faster +* Integer operations (with float conversion): 3-5x faster +* Integer scale-invariant operations (native int): 6-12x faster +* Half-float operations: 3-5x faster + +Actual performance depends on the specific operation, image size, data types, +and hardware capabilities (AVX2, AVX-512, ARM NEON, etc.). + .. _sec-iba-patterns: diff --git a/src/doc/imageioapi.rst b/src/doc/imageioapi.rst index d2d6b192b4..1c57c39383 100644 --- a/src/doc/imageioapi.rst +++ b/src/doc/imageioapi.rst @@ -215,49 +215,12 @@ These helper functions are not part of any other OpenImageIO class, they just exist in the OpenImageIO namespace as general utilities. (See :ref:`sec-pythonmiscapi` for the corresponding Python bindings.) -.. doxygenfunction:: OIIO::attribute(string_view, TypeDesc, const void *) - -.. cpp:function:: bool OIIO::attribute(string_view name, int val) - bool OIIO::attribute(string_view name, float val) - bool OIIO::attribute(string_view name, string_view val) - - Shortcuts for setting an attribute to a single int, float, or string. - - -.. doxygenfunction:: OIIO::getattribute(string_view, TypeDesc, void *) - - -.. cpp:function:: bool getattribute (string_view name, int &val) - bool getattribute (string_view name, float &val) - bool getattribute (string_view name, char **val) - bool getattribute (string_view name, std::string& val) - - Specialized versions of `getattribute()` in which the data type is - implied by the type of the argument (for single int, float, or string). - Two string versions exist: one that retrieves it as a `std::string` and - another that retrieves it as a `char *`. In all cases, the return value - is `true` if the attribute is found and the requested data type - conversion was legal. - - EXAMPLES:: - - int threads; - OIIO::getattribute ("threads", &threads); - std::string path; - OIIO::getattribute ("plugin_searchpath", path); - -.. cpp:function:: int get_int_attribute (string_view name, int defaultvalue=0) - float get_float_attribute (string_view name, float defaultvalue=0) - string_view get_string_attribute (string_view name, string_view defaultvalue="") - - Specialized versions of `getattribute()` for common types, in which the - data is returned directly, and a supplied default value is returned if - the attribute was not found. +.. doxygengroup:: OIIO_attribute +.. - EXAMPLES:: - int threads = OIIO::get_int_attribute ("threads", 0); - string_view path = OIIO::get_string_attribute ("plugin_searchpath"); +.. doxygengroup:: OIIO_getattribute +.. @@ -397,16 +360,36 @@ inside the source code. line, but not the full human-readable command line. (This was added in OpenImageIO 2.5.11.) +.. cpp:var:: OPENIMAGEIO_ENABLE_HWY + + Controls whether to use Google Highway SIMD library optimizations for + ImageBufAlgo operations. If set to "1" (the default), Highway SIMD + optimizations will be enabled for supported operations, providing + significant performance improvements (typically 3-12x faster) on integer + image types. If set to "0", these optimizations will be disabled and fall + back to scalar implementations. + + This can also be controlled at runtime via:: + + OIIO::attribute("enable_hwy", 1); // enable (default) + OIIO::attribute("enable_hwy", 0); // disable + + Note: Highway SIMD optimizations are particularly beneficial for integer + image formats (uint8, uint16, int8, int16, uint32, int32, etc.) and provide + additional speedup for scale-invariant operations (add, sub, min, max, + absdiff) that can operate directly on integer data without float conversion. + (This was added in OpenImageIO 3.1.) + .. cpp:var:: OPENIMAGEIO_PYTHON_LOAD_DLLS_FROM_PATH - Windows only. Mimics the DLL-loading behavior of Python 3.7 and earlier. - If set to "1", all directories under ``PATH`` will be added to the DLL load + Windows only. Mimics the DLL-loading behavior of Python 3.7 and earlier. + If set to "1", all directories under ``PATH`` will be added to the DLL load path before attempting to import the OpenImageIO module. (This was added in OpenImageIO 3.0.3.0) - Note: This "opt-in-style" behavior replaces and inverts the "opt-out-style" - Windows DLL-loading behavior governed by the now-defunct `OIIO_LOAD_DLLS_FROM_PATH` - environment variable (added in OpenImageIO 2.4.0/2.3.18). + Note: This "opt-in-style" behavior replaces and inverts the "opt-out-style" + Windows DLL-loading behavior governed by the now-defunct `OIIO_LOAD_DLLS_FROM_PATH` + environment variable (added in OpenImageIO 2.4.0/2.3.18). - In other words, to reproduce the default Python-module-loading behavior of + In other words, to reproduce the default Python-module-loading behavior of earlier versions of OIIO, set ``OPENIMAGEIO_PYTHON_LOAD_DLLS_FROM_PATH=1``. diff --git a/src/dpx.imageio/dpxoutput.cpp b/src/dpx.imageio/dpxoutput.cpp index 4db1103a3c..719a38da1a 100644 --- a/src/dpx.imageio/dpxoutput.cpp +++ b/src/dpx.imageio/dpxoutput.cpp @@ -46,9 +46,6 @@ class DPXOutput final : public ImageOutput { bool close() override; bool write_scanline(int y, int z, TypeDesc format, const void* data, stride_t xstride) override; - bool write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, - stride_t zstride) override; private: OutStream* m_stream = nullptr; @@ -69,7 +66,6 @@ class DPXOutput final : public ImageOutput { std::vector m_subimage_specs; bool m_write_pending; // subimage buffer needs to be written unsigned int m_dither; - std::vector m_tilebuffer; // Initialize private members to pre-opened state void init(void) @@ -417,11 +413,6 @@ DPXOutput::open(const std::string& name, const ImageSpec& userspec, ? spec0.get_int_attribute("oiio:dither", 0) : 0; - // If user asked for tiles -- which this format doesn't support, emulate - // it by buffering the whole image. - if (spec0.tile_width && spec0.tile_height) - m_tilebuffer.resize(spec0.image_bytes()); - return prep_subimage(m_subimage, true); } @@ -593,16 +584,7 @@ DPXOutput::close() return true; } - bool ok = true; - const ImageSpec& spec_s(m_subimage_specs[m_subimage]); - if (spec_s.tile_width && m_tilebuffer.size()) { - // Handle tile emulation -- output the buffered pixels - ok &= write_scanlines(spec_s.y, spec_s.y + spec_s.height, 0, - spec_s.format, &m_tilebuffer[0]); - std::vector().swap(m_tilebuffer); - } - - ok &= write_buffer(); + bool ok = write_buffer(); m_dpx.Finish(); init(); // Reset to initial state return ok; @@ -644,22 +626,6 @@ DPXOutput::write_scanline(int y, int z, TypeDesc format, const void* data, -bool -DPXOutput::write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, stride_t zstride) -{ - if (!is_opened()) { - errorfmt("write_tile called but file is not open."); - return false; - } - - // Emulate tiles by buffering the whole image - return copy_tile_to_image_buffer(x, y, z, format, data, xstride, ystride, - zstride, &m_tilebuffer[0]); -} - - - dpx::Characteristic DPXOutput::get_characteristic_from_string(const std::string& str) { diff --git a/src/ffmpeg.imageio/CMakeLists.txt b/src/ffmpeg.imageio/CMakeLists.txt index c84ef3c90a..ac76c21473 100644 --- a/src/ffmpeg.imageio/CMakeLists.txt +++ b/src/ffmpeg.imageio/CMakeLists.txt @@ -28,7 +28,7 @@ if (FFmpeg_FOUND) add_oiio_plugin (ffmpeginput.cpp INCLUDE_DIRS ${FFMPEG_INCLUDES} LINK_LIBRARIES ${FFMPEG_LIBRARIES} - ${BZIP2_LIBRARIES} + $ DEFINITIONS "USE_FFMPEG" "-DOIIO_FFMPEG_VERSION=\"${FFMPEG_VERSION}\"") else() diff --git a/src/hdr.imageio/hdrinput.cpp b/src/hdr.imageio/hdrinput.cpp index 850899d89d..9627b9b3ed 100644 --- a/src/hdr.imageio/hdrinput.cpp +++ b/src/hdr.imageio/hdrinput.cpp @@ -304,12 +304,7 @@ HdrInput::RGBE_ReadHeader() found_FORMAT_line = true; /* LG says no: break; // format found so break out of loop */ } else if (Strutil::parse_values(line, "GAMMA=", span(tempf))) { - // Round gamma to the nearest hundredth to prevent stupid - // precision choices and make it easier for apps to make - // decisions based on known gamma values. For example, you want - // 2.2, not 2.19998. float g = float(1.0 / tempf); - g = roundf(100.0 * g) / 100.0f; set_colorspace_rec709_gamma(m_spec, g); } else if (Strutil::parse_values(line, "EXPOSURE=", span(tempf))) { diff --git a/src/hdr.imageio/hdroutput.cpp b/src/hdr.imageio/hdroutput.cpp index 5feb97042a..e682a20079 100644 --- a/src/hdr.imageio/hdroutput.cpp +++ b/src/hdr.imageio/hdroutput.cpp @@ -26,14 +26,10 @@ class HdrOutput final : public ImageOutput { OpenMode mode) override; bool write_scanline(int y, int z, TypeDesc format, const void* data, stride_t xstride) override; - bool write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, - stride_t zstride) override; bool close() override; private: std::vector scratch; - std::vector m_tilebuffer; void init(void) { ioproxy_clear(); } @@ -226,11 +222,6 @@ HdrOutput::open(const std::string& name, const ImageSpec& newspec, if (!iowritefmt("-Y {} +X {}\n", m_spec.height, m_spec.width)) return false; - // If user asked for tiles -- which this format doesn't support, emulate - // it by buffering the whole image. - if (m_spec.tile_width && m_spec.tile_height) - m_tilebuffer.resize(m_spec.image_bytes()); - return true; } @@ -246,17 +237,6 @@ HdrOutput::write_scanline(int /*y*/, int /*z*/, TypeDesc format, -bool -HdrOutput::write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, stride_t zstride) -{ - // Emulate tiles by buffering the whole image - return copy_tile_to_image_buffer(x, y, z, format, data, xstride, ystride, - zstride, &m_tilebuffer[0]); -} - - - bool HdrOutput::close() { @@ -265,18 +245,9 @@ HdrOutput::close() return true; } - bool ok = true; - if (m_spec.tile_width) { - // We've been emulating tiles; now dump as scanlines. - OIIO_ASSERT(m_tilebuffer.size()); - ok &= write_scanlines(m_spec.y, m_spec.y + m_spec.height, 0, - m_spec.format, &m_tilebuffer[0]); - std::vector().swap(m_tilebuffer); - } - init(); - return ok; + return true; } OIIO_PLUGIN_NAMESPACE_END diff --git a/src/heif.imageio/CMakeLists.txt b/src/heif.imageio/CMakeLists.txt index 25606a1391..c79b544035 100644 --- a/src/heif.imageio/CMakeLists.txt +++ b/src/heif.imageio/CMakeLists.txt @@ -3,31 +3,27 @@ # https://github.com/AcademySoftwareFoundation/OpenImageIO if (Libheif_FOUND) - if (LINKSTATIC) - set (_static_suffixes .lib .a) - set (_static_libraries_found 0) - - foreach (_libeheif_library IN LISTS LIBHEIF_LIBRARIES) - get_filename_component (_ext ${_libeheif_library} LAST_EXT) - list (FIND _static_suffixes ${_ext} _index) - if (${_index} GREATER -1) - MATH (EXPR _static_libraries_found "${static_libraries_found}+1") - endif() - endforeach() - - if (${_static_libraries_found} GREATER 0) - message (STATUS "${ColorYellow}") - message (STATUS "You are linking OpenImageIO against a static version of libheif, which is LGPL") - message (STATUS "licensed. If you intend to redistribute this build of OpenImageIO, we recommend") - message (STATUS "that you review the libheif license terms, or you may wish to switch to using a") - message (STATUS "dynamically-linked libheif.") - message ("${ColorReset}") + # Some extra care is needed if the libheif we found was static + set (_static_suffixes .lib .a) + set (_static_libraries_found 0) + foreach (_libeheif_library IN LISTS LIBHEIF_LIBRARIES) + get_filename_component (_ext ${_libeheif_library} LAST_EXT) + list (FIND _static_suffixes ${_ext} _index) + if (${_index} GREATER -1) + MATH (EXPR _static_libraries_found "${_static_libraries_found}+1") endif() + endforeach() + if (${_static_libraries_found} GREATER 0) + message (STATUS "${ColorYellow}") + message (STATUS "You are linking OpenImageIO against a static version of libheif, which is LGPL") + message (STATUS "licensed. If you intend to redistribute this build of OpenImageIO, we recommend") + message (STATUS "that you review the libheif license terms, or you may wish to switch to using a") + message (STATUS "dynamically-linked libheif.") + message ("${ColorReset}") endif() add_oiio_plugin (heifinput.cpp heifoutput.cpp - INCLUDE_DIRS ${LIBHEIF_INCLUDES} - LINK_LIBRARIES ${LIBHEIF_LIBRARIES} + LINK_LIBRARIES heif DEFINITIONS "USE_HEIF=1") else () message (WARNING "heif plugin will not be built") diff --git a/src/heif.imageio/heifinput.cpp b/src/heif.imageio/heifinput.cpp index 349bcdb1d4..a49350a215 100644 --- a/src/heif.imageio/heifinput.cpp +++ b/src/heif.imageio/heifinput.cpp @@ -32,6 +32,35 @@ OIIO_PLUGIN_NAMESPACE_BEGIN + +class HeifReader final : public heif::Context::Reader { +public: + HeifReader(Filesystem::IOProxy* ioproxy) + : m_ioproxy(ioproxy) + { + m_ioproxy->seek(0); + } + int64_t get_position() const override { return m_ioproxy->tell(); } + int read(void* data, size_t size) override + { + return m_ioproxy->read(data, size) == size ? 0 : -1; + } + int seek(int64_t position) override + { + return m_ioproxy->seek(position) ? 0 : -1; + } + heif_reader_grow_status wait_for_file_size(int64_t target_size) override + { + return target_size <= int64_t(m_ioproxy->size()) + ? heif_reader_grow_status_size_reached + : heif_reader_grow_status_size_beyond_eof; + } + +private: + Filesystem::IOProxy* m_ioproxy; +}; + + class HeifInput final : public ImageInput { public: HeifInput() {} @@ -39,13 +68,13 @@ class HeifInput final : public ImageInput { const char* format_name(void) const override { return "heif"; } int supports(string_view feature) const override { - return feature == "exif" + return feature == "exif" || feature == "ioproxy" #if LIBHEIF_HAVE_VERSION(1, 9, 0) || feature == "cicp" #endif ; } - bool valid_file(const std::string& filename) const override; + bool valid_file(Filesystem::IOProxy* ioproxy) const override; bool open(const std::string& name, ImageSpec& newspec) override; bool open(const std::string& name, ImageSpec& newspec, const ImageSpec& config) override; @@ -67,6 +96,7 @@ class HeifInput final : public ImageInput { bool m_do_associate = false; bool m_reorient = true; std::unique_ptr m_ctx; + std::unique_ptr m_reader; heif_item_id m_primary_id; // id of primary image std::vector m_item_ids; // ids of all other images heif::ImageHandle m_ihandle; @@ -74,7 +104,6 @@ class HeifInput final : public ImageInput { }; - void oiio_heif_init() { @@ -111,10 +140,12 @@ OIIO_PLUGIN_EXPORTS_END bool -HeifInput::valid_file(const std::string& filename) const +HeifInput::valid_file(Filesystem::IOProxy* ioproxy) const { + if (!ioproxy || ioproxy->mode() != Filesystem::IOProxy::Mode::Read) + return false; uint8_t magic[12]; - if (Filesystem::read_bytes(filename, magic, sizeof(magic)) != sizeof(magic)) + if (ioproxy->pread(magic, sizeof(magic), 0) != sizeof(magic)) return false; heif_filetype_result filetype_check = heif_check_filetype(magic, sizeof(magic)); @@ -141,7 +172,12 @@ HeifInput::open(const std::string& name, ImageSpec& newspec, m_filename = name; m_subimage = -1; + ioproxy_retrieve_from_config(config); + if (!ioproxy_use_or_open(name)) + return false; + m_ctx.reset(new heif::Context); + m_reader.reset(new HeifReader(ioproxy())); m_himage = heif::Image(); m_ihandle = heif::ImageHandle(); @@ -150,8 +186,7 @@ HeifInput::open(const std::string& name, ImageSpec& newspec, m_reorient = config.get_int_attribute("oiio:reorient", 1); try { - m_ctx->read_from_file(name); - // FIXME: should someday be read_from_reader to give full flexibility + m_ctx->read_from_reader(*m_reader); m_item_ids = m_ctx->get_list_of_top_level_image_IDs(); m_primary_id = m_ctx->get_primary_image_ID(); @@ -187,6 +222,7 @@ HeifInput::close() m_himage = heif::Image(); m_ihandle = heif::ImageHandle(); m_ctx.reset(); + m_reader.reset(); m_subimage = -1; m_num_subimages = 0; m_associated_alpha = true; @@ -226,15 +262,40 @@ HeifInput::seek_subimage(int subimage, int miplevel) } m_has_alpha = m_ihandle.has_alpha_channel(); - auto chroma = m_has_alpha ? (m_bitdepth > 8) - ? littleendian() - ? heif_chroma_interleaved_RRGGBBAA_LE - : heif_chroma_interleaved_RRGGBBAA_BE - : heif_chroma_interleaved_RGBA - : (m_bitdepth > 8) ? littleendian() - ? heif_chroma_interleaved_RRGGBB_LE - : heif_chroma_interleaved_RRGGBB_BE - : heif_chroma_interleaved_RGB; + + bool is_monochrome = false; + +#if LIBHEIF_NUMERIC_VERSION >= MAKE_LIBHEIF_VERSION(1, 17, 0, 0) + heif_colorspace preferred_colorspace = heif_colorspace_undefined; + heif_chroma preferred_chroma = heif_chroma_undefined; + + if (heif_image_handle_get_preferred_decoding_colorspace( + m_ihandle.get_raw_image_handle(), &preferred_colorspace, + &preferred_chroma) + .code + == heif_error_Ok) { + is_monochrome = preferred_colorspace == heif_colorspace_monochrome; + } +#endif + + const heif_chroma chroma + = (is_monochrome) ? heif_chroma_monochrome + : m_has_alpha ? (m_bitdepth > 8) + ? littleendian() + ? heif_chroma_interleaved_RRGGBBAA_LE + : heif_chroma_interleaved_RRGGBBAA_BE + : heif_chroma_interleaved_RGBA + : (m_bitdepth > 8) ? littleendian() + ? heif_chroma_interleaved_RRGGBB_LE + : heif_chroma_interleaved_RRGGBB_BE + : heif_chroma_interleaved_RGB; + const heif_colorspace colorspace = is_monochrome + ? heif_colorspace_monochrome + : heif_colorspace_RGB; + const heif_channel channel = is_monochrome ? heif_channel_Y + : heif_channel_interleaved; + const int nchannels = is_monochrome ? 1 : m_has_alpha ? 4 : 3; + #if 0 try { m_himage = m_ihandle.decode_image(heif_colorspace_RGB, chroma); @@ -254,8 +315,8 @@ HeifInput::seek_subimage(int subimage, int miplevel) // print("Got decoding options version {}\n", options->version); struct heif_image* img_tmp = nullptr; struct heif_error herr = heif_decode_image(m_ihandle.get_raw_image_handle(), - &img_tmp, heif_colorspace_RGB, - chroma, options.get()); + &img_tmp, colorspace, chroma, + options.get()); if (img_tmp) m_himage = heif::Image(img_tmp); if (herr.code != heif_error_Ok || !img_tmp) { @@ -265,9 +326,8 @@ HeifInput::seek_subimage(int subimage, int miplevel) } #endif - m_spec = ImageSpec(m_himage.get_width(heif_channel_interleaved), - m_himage.get_height(heif_channel_interleaved), - m_has_alpha ? 4 : 3, + m_spec = ImageSpec(m_himage.get_width(channel), + m_himage.get_height(channel), nchannels, (m_bitdepth > 8) ? TypeUInt16 : TypeUInt8); if (m_bitdepth > 8) { @@ -287,7 +347,14 @@ HeifInput::seek_subimage(int subimage, int miplevel) m_ihandle.get_raw_image_handle(), &nclx); if (nclx) { - if (err.code == heif_error_Ok) { + // When CICP metadata is not present in the file, libheif returns + // unspecified since v1.21. Ignore it then. + if (err.code == heif_error_Ok + && !(nclx->color_primaries == heif_color_primaries_unspecified + && nclx->transfer_characteristics + == heif_transfer_characteristic_unspecified + && nclx->matrix_coefficients + == heif_matrix_coefficients_unspecified)) { const int cicp[4] = { int(nclx->color_primaries), int(nclx->transfer_characteristics), int(nclx->matrix_coefficients), @@ -449,12 +516,13 @@ HeifInput::read_native_scanline(int subimage, int miplevel, int y, int /*z*/, #else int ystride = 0; #endif + const heif_channel channel = m_spec.nchannels == 1 + ? heif_channel_Y + : heif_channel_interleaved; #if LIBHEIF_NUMERIC_VERSION >= MAKE_LIBHEIF_VERSION(1, 20, 2, 0) - const uint8_t* hdata = m_himage.get_plane2(heif_channel_interleaved, - &ystride); + const uint8_t* hdata = m_himage.get_plane2(channel, &ystride); #else - const uint8_t* hdata = m_himage.get_plane(heif_channel_interleaved, - &ystride); + const uint8_t* hdata = m_himage.get_plane(channel, &ystride); #endif if (!hdata) { errorfmt("Unknown read error"); diff --git a/src/heif.imageio/heifoutput.cpp b/src/heif.imageio/heifoutput.cpp index 9998ab5a5f..2a2fcf2744 100644 --- a/src/heif.imageio/heifoutput.cpp +++ b/src/heif.imageio/heifoutput.cpp @@ -29,7 +29,8 @@ class HeifOutput final : public ImageOutput { const char* format_name(void) const override { return "heif"; } int supports(string_view feature) const override { - return feature == "alpha" || feature == "exif" || feature == "tiles" + return feature == "alpha" || feature == "exif" || feature == "ioproxy" + || feature == "tiles" #if LIBHEIF_HAVE_VERSION(1, 9, 0) || feature == "cicp" #endif @@ -49,19 +50,18 @@ class HeifOutput final : public ImageOutput { std::unique_ptr m_ctx; heif::ImageHandle m_ihandle; heif::Image m_himage; - heif::Encoder m_encoder { heif_compression_HEVC }; + // Undefined until we know the specific requested encoder, because an + // exception is thrown if libheif is built without support for it. + heif::Encoder m_encoder { heif_compression_undefined }; std::vector scratch; std::vector m_tilebuffer; int m_bitdepth = 0; }; - -namespace { - -class MyHeifWriter final : public heif::Context::Writer { +class HeifWriter final : public heif::Context::Writer { public: - MyHeifWriter(Filesystem::IOProxy* ioproxy) + HeifWriter(Filesystem::IOProxy* ioproxy) : m_ioproxy(ioproxy) { } @@ -82,9 +82,6 @@ class MyHeifWriter final : public heif::Context::Writer { Filesystem::IOProxy* m_ioproxy = nullptr; }; -} // namespace - - OIIO_PLUGIN_EXPORTS_BEGIN @@ -112,6 +109,11 @@ HeifOutput::open(const std::string& name, const ImageSpec& newspec, m_filename = name; + ioproxy_retrieve_from_config(m_spec); + if (!ioproxy_use_or_open(name)) { + return false; + } + m_bitdepth = m_spec.format.size() > TypeUInt8.size() ? 10 : 8; m_bitdepth = m_spec.get_int_attribute("oiio:BitsPerSample", m_bitdepth); if (m_bitdepth == 10 || m_bitdepth == 12) { @@ -126,7 +128,7 @@ HeifOutput::open(const std::string& name, const ImageSpec& newspec, try { m_ctx.reset(new heif::Context); m_himage = heif::Image(); - static heif_chroma chromas[/*nchannels*/] + const heif_chroma chromas[/*nchannels*/] = { heif_chroma_undefined, heif_chroma_monochrome, heif_chroma_undefined, (m_bitdepth == 8) ? heif_chroma_interleaved_RGB @@ -135,25 +137,34 @@ HeifOutput::open(const std::string& name, const ImageSpec& newspec, (m_bitdepth == 8) ? heif_chroma_interleaved_RGBA : littleendian() ? heif_chroma_interleaved_RRGGBBAA_LE : heif_chroma_interleaved_RRGGBBAA_BE }; - m_himage.create(newspec.width, newspec.height, heif_colorspace_RGB, + const heif_colorspace colorspace = (m_spec.nchannels == 1) + ? heif_colorspace_monochrome + : heif_colorspace_RGB; + const heif_channel channel = (m_spec.nchannels == 1) + ? heif_channel_Y + : heif_channel_interleaved; + + m_himage.create(newspec.width, newspec.height, colorspace, chromas[m_spec.nchannels]); - m_himage.add_plane(heif_channel_interleaved, newspec.width, - newspec.height, m_bitdepth); + m_himage.add_plane(channel, newspec.width, newspec.height, m_bitdepth); - m_encoder = heif::Encoder(heif_compression_HEVC); auto compqual = m_spec.decode_compression_metadata("", 75); auto extension = Filesystem::extension(m_filename); if (compqual.first == "avif" || (extension == ".avif" && compqual.first == "")) { m_encoder = heif::Encoder(heif_compression_AV1); + } else { + m_encoder = heif::Encoder(heif_compression_HEVC); } } catch (const heif::Error& err) { std::string e = err.get_message(); errorfmt("{}", e.empty() ? "unknown exception" : e.c_str()); + m_ctx.reset(); return false; } catch (const std::exception& err) { std::string e = err.what(); errorfmt("{}", e.empty() ? "unknown exception" : e.c_str()); + m_ctx.reset(); return false; } @@ -177,10 +188,13 @@ HeifOutput::write_scanline(int y, int /*z*/, TypeDesc format, const void* data, #else int hystride = 0; #endif + const heif_channel hchannel = (m_spec.nchannels == 1) + ? heif_channel_Y + : heif_channel_interleaved; #if LIBHEIF_NUMERIC_VERSION >= MAKE_LIBHEIF_VERSION(1, 20, 2, 0) - uint8_t* hdata = m_himage.get_plane2(heif_channel_interleaved, &hystride); + uint8_t* hdata = m_himage.get_plane2(hchannel, &hystride); #else - uint8_t* hdata = m_himage.get_plane(heif_channel_interleaved, &hystride); + uint8_t* hdata = m_himage.get_plane(hchannel, &hystride); #endif hdata += hystride * (y - m_spec.y); if (m_bitdepth == 10 || m_bitdepth == 12) { @@ -218,7 +232,9 @@ HeifOutput::write_tile(int x, int y, int z, TypeDesc format, const void* data, bool HeifOutput::close() { - if (!m_ctx) { // already closed + if (!m_ctx || !ioproxy_opened()) { // already closed + m_ctx.reset(); + ioproxy_clear(); return true; } @@ -283,25 +299,20 @@ HeifOutput::close() #endif } m_ctx->set_primary_image(m_ihandle); - Filesystem::IOFile ioproxy(m_filename, Filesystem::IOProxy::Write); - if (ioproxy.mode() != Filesystem::IOProxy::Write) { - errorfmt("Could not open \"{}\"", m_filename); - ok = false; - } else { - MyHeifWriter writer(&ioproxy); - m_ctx->write(writer); - } + HeifWriter writer(ioproxy()); + m_ctx->write(writer); } catch (const heif::Error& err) { std::string e = err.get_message(); errorfmt("{}", e.empty() ? "unknown exception" : e.c_str()); - return false; + ok = false; } catch (const std::exception& err) { std::string e = err.what(); errorfmt("{}", e.empty() ? "unknown exception" : e.c_str()); - return false; + ok = false; } m_ctx.reset(); + ioproxy_clear(); return ok; } diff --git a/src/ico.imageio/icooutput.cpp b/src/ico.imageio/icooutput.cpp index 18e5fdb5a3..b6f3eed9de 100644 --- a/src/ico.imageio/icooutput.cpp +++ b/src/ico.imageio/icooutput.cpp @@ -32,9 +32,6 @@ class ICOOutput final : public ImageOutput { bool close() override; bool write_scanline(int y, int z, TypeDesc format, const void* data, stride_t xstride) override; - bool write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, - stride_t zstride) override; private: std::string m_filename; ///< Stash the filename @@ -47,7 +44,6 @@ class ICOOutput final : public ImageOutput { int m_and_slb; ///< AND mask scanline length in bytes int m_bpp; ///< Bits per pixel unsigned int m_dither; - std::vector m_tilebuffer; png_structp m_png; ///< PNG read structure pointer png_infop m_info; ///< PNG image info structure pointer @@ -361,11 +357,6 @@ ICOOutput::open(const std::string& name, const ImageSpec& userspec, fseek(m_file, m_offset + sizeof(bmi), SEEK_SET); } - // If user asked for tiles -- which this format doesn't support, emulate - // it by buffering the whole image. - if (m_spec.tile_width && m_spec.tile_height) - m_tilebuffer.resize(m_spec.image_bytes()); - return true; } @@ -392,15 +383,6 @@ ICOOutput::close() return true; } - bool ok = true; - if (m_spec.tile_width) { - // Handle tile emulation -- output the buffered pixels - OIIO_ASSERT(m_tilebuffer.size()); - ok &= write_scanlines(m_spec.y, m_spec.y + m_spec.height, 0, - m_spec.format, &m_tilebuffer[0]); - std::vector().swap(m_tilebuffer); - } - if (m_png) { PNG_pvt::write_end(m_png, m_info); if (m_png || m_info) @@ -411,7 +393,7 @@ ICOOutput::close() fclose(m_file); m_file = NULL; init(); // re-initialize - return ok; + return true; } @@ -516,14 +498,4 @@ ICOOutput::write_scanline(int y, int z, TypeDesc format, const void* data, -bool -ICOOutput::write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, stride_t zstride) -{ - // Emulate tiles by buffering the whole image - return copy_tile_to_image_buffer(x, y, z, format, data, xstride, ystride, - zstride, &m_tilebuffer[0]); -} - - OIIO_PLUGIN_NAMESPACE_END diff --git a/src/include/OpenImageIO/benchmark.h b/src/include/OpenImageIO/benchmark.h index bfcb8b8235..47727a7513 100644 --- a/src/include/OpenImageIO/benchmark.h +++ b/src/include/OpenImageIO/benchmark.h @@ -15,7 +15,7 @@ #include -#if (((OIIO_GNUC_VERSION && NDEBUG) || OIIO_CLANG_VERSION >= 30500 || OIIO_APPLE_CLANG_VERSION >= 70000 || defined(__INTEL_COMPILER) || defined(__INTEL_LLVM_COMPILER)) \ +#if (((OIIO_GNUC_VERSION && NDEBUG) || OIIO_CLANG_VERSION >= 30500 || OIIO_APPLE_CLANG_VERSION >= 70000 || defined(__INTEL_LLVM_COMPILER)) \ && (defined(__x86_64__) || defined(__i386__))) \ || defined(_MSC_VER) #define OIIO_DONOTOPT_FORECINLINE OIIO_FORCEINLINE diff --git a/src/include/OpenImageIO/bit.h b/src/include/OpenImageIO/bit.h index 08526f3b85..520f35ba93 100644 --- a/src/include/OpenImageIO/bit.h +++ b/src/include/OpenImageIO/bit.h @@ -37,40 +37,6 @@ bitcast(const From& from) noexcept return result; } -#if defined(__INTEL_COMPILER) -// For Intel icc, using the memcpy implementation above will cause a loop with -// a bitcast to fail to vectorize, but using the intrinsics below will allow -// it to vectorize. For icx, as well as gcc and clang, the same optimal code -// is generated (even in a vectorized loop) for memcpy. We can probably remove -// these intrinsics once we drop support for icc. -template<> -OIIO_NODISCARD OIIO_FORCEINLINE uint32_t -bitcast(const float& val) noexcept -{ - return static_cast(_castf32_u32(val)); -} - -template<> -OIIO_NODISCARD OIIO_FORCEINLINE int32_t -bitcast(const float& val) noexcept -{ - return static_cast(_castf32_u32(val)); -} - -template<> -OIIO_NODISCARD OIIO_FORCEINLINE float -bitcast(const uint32_t& val) noexcept -{ - return _castu32_f32(val); -} - -template<> -OIIO_NODISCARD OIIO_FORCEINLINE float -bitcast(const int32_t& val) noexcept -{ - return _castu32_f32(val); -} -#endif OIIO_NODISCARD OIIO_FORCEINLINE OIIO_HOSTDEVICE int @@ -112,9 +78,7 @@ byteswap(T n) -#if (OIIO_GNUC_VERSION || OIIO_ANY_CLANG \ - || OIIO_INTEL_CLASSIC_COMPILER_VERSION) \ - && !defined(__CUDACC__) +#if (OIIO_GNUC_VERSION || OIIO_ANY_CLANG) && !defined(__CUDACC__) // CPU gcc and compatible can use these intrinsics, 8-15x faster template<> diff --git a/src/include/OpenImageIO/dassert.h b/src/include/OpenImageIO/dassert.h index db7cf65976..db3e8992b9 100644 --- a/src/include/OpenImageIO/dassert.h +++ b/src/include/OpenImageIO/dassert.h @@ -9,9 +9,161 @@ #include #include +#include #include + +// General resources about security and hardening for C++: +// +// https://best.openssf.org/Compiler-Hardening-Guides/Compiler-Options-Hardening-Guide-for-C-and-C++.html +// https://www.gnu.org/software/libc/manual/html_node/Source-Fortification.html +// https://gcc.gnu.org/onlinedocs/libstdc++/manual/using_macros.html +// https://libcxx.llvm.org/Hardening.html +// https://cheatsheetseries.owasp.org/cheatsheets/C-Based_Toolchain_Hardening_Cheat_Sheet.html +// https://stackoverflow.com/questions/13544512/what-is-the-most-hardened-set-of-options-for-gcc-compiling-c-c +// https://medium.com/@simontoth/daily-bit-e-of-c-hardened-mode-of-standard-library-implementations-18be2422c372 +// https://en.cppreference.com/w/cpp/contract +// https://en.cppreference.com/w/cpp/language/contracts + + + +// Define hardening levels for OIIO: which checks should we do? +// NONE - YOLO mode, no extra checks (not recommended) +// FAST - Minimal checks that have low performance impact +// EXTENSIVE - More thorough checks, may impact performance +// DEBUG - Maximum checks, for debugging purposes +#define OIIO_HARDENING_NONE 0 +#define OIIO_HARDENING_FAST 1 +#define OIIO_HARDENING_EXTENSIVE 2 +#define OIIO_HARDENING_DEBUG 3 + +// OIIO_HARDENING_DEFAULT defines the default hardening level we actually use. +// By default, we use FAST for release builds and DEBUG for debug builds. But +// it can be overridden: +// - For OIIO internals, at OIIO build time with the `OIIO_HARDENING` CMake +// variable. +// - For other projects using OIIO's headers, any translation unit may +// override this by defining OIIO_HARDENING_DEFAULT before including any +// OIIO headers. But note that this only affects calls to inline functions +// or templates defined in the headers. Non-inline functions compiled into +// the OIIO library itself will have been compiled with whatever hardening +// level was selected when the library was built. +#ifndef OIIO_HARDENING_DEFAULT +# ifdef NDEBUG +# define OIIO_HARDENING_DEFAULT OIIO_HARDENING_FAST +# else +# define OIIO_HARDENING_DEFAULT OIIO_HARDENING_DEBUG +# endif +#endif + + +// Choices for what to do when a contract assertion fails. +// This mimics the C++26 standard's std::contract behavior. +#define OIIO_ASSERTION_RESPONSE_IGNORE 0 +#define OIIO_ASSERTION_RESPONSE_OBSERVE 1 +#define OIIO_ASSERTION_RESPONSE_ENFORCE 2 +#define OIIO_ASSERTION_RESPONSE_QUICK_ENFORCE 3 + +// OIIO_ASSERTION_RESPONSE_DEFAULT defines the default response to failed +// contract assertions. By default, we enforce them, UNLESS we are a release +// mode build that has set the hardening mode to NONE. But any translation +// unit (including clients of OIIO) may override this by defining +// OIIO_ASSERTION_RESPONSE_DEFAULT before including any OIIO headers. But note +// that this only affects calls to inline functions or templates defined in +// the headers. Non-inline functions compiled into the OIIO library itself +// will have been compiled with whatever response was selected when the +// library was built. +#ifndef OIIO_ASSERTION_RESPONSE_DEFAULT +# if OIIO_HARDENING_DEFAULT == OIIO_HARDENING_NONE && defined(NDEBUG) +# define OIIO_ASSERTION_RESPONSE_DEFAULT OIIO_ASSERTION_RESPONSE_IGNORE +# else +# define OIIO_ASSERTION_RESPONSE_DEFAULT OIIO_ASSERTION_RESPONSE_ENFORCE +# endif +#endif + + + +// `OIIO_CONTRACT_ASSERT(condition)` checks if the condition is met, and if +// not, calls the contract violation handler with the appropriate response. +// `OIIO_CONTRACT_ASSERT_MSG(condition, msg)` is the same, but allows a +// different message to be passed to the handler. +#if OIIO_ASSERTION_RESPONSE_DEFAULT == OIIO_ASSERTION_RESPONSE_IGNORE +# define OIIO_CONTRACT_ASSERT_MSG(condition, message) (void)0 +#elif OIIO_ASSERTION_RESPONSE_DEFAULT == OIIO_ASSERTION_RESPONSE_QUICK_ENFORCE +# define OIIO_CONTRACT_ASSERT_MSG(condition, message) \ + (OIIO_LIKELY(condition) ? ((void)0) : (std::abort(), (void)0)) +#elif OIIO_ASSERTION_RESPONSE_DEFAULT == OIIO_ASSERTION_RESPONSE_OBSERVE +# define OIIO_CONTRACT_ASSERT_MSG(condition, message) \ + (OIIO_LIKELY(condition) ? ((void)0) \ + : (OIIO::contract_violation_handler( \ + __FILE__ ":" OIIO_STRINGIZE(__LINE__), \ + OIIO_PRETTY_FUNCTION, message), \ + (void)0)) +#elif OIIO_ASSERTION_RESPONSE_DEFAULT == OIIO_ASSERTION_RESPONSE_ENFORCE +# define OIIO_CONTRACT_ASSERT_MSG(condition, message) \ + (OIIO_LIKELY(condition) ? ((void)0) \ + : (OIIO::contract_violation_handler( \ + __FILE__ ":" OIIO_STRINGIZE(__LINE__), \ + OIIO_PRETTY_FUNCTION, message), \ + std::abort(), (void)0)) +#else +# error "Unknown OIIO_ASSERTION_RESPONSE_DEFAULT" +#endif + +#define OIIO_CONTRACT_ASSERT(condition) \ + OIIO_CONTRACT_ASSERT_MSG(condition, #condition) + +// Macros to use to select whether or not to do a contract check, based on the +// hardening level: +// - OIIO_HARDENING_ASSERT_FAST : only checks contract for >= FAST hardening. +// - OIIO_HARDENING_ASSERT_EXTENSIVE : only checks contract for >= EXTENSIVE. +// - OIIO_HARDENING_ASSERT_DEBUG : only checks contract for DEBUG hardening. +#if OIIO_HARDENING_DEFAULT >= OIIO_HARDENING_FAST +# define OIIO_HARDENING_ASSERT_FAST_MSG(condition, message) \ + OIIO_CONTRACT_ASSERT_MSG(condition, message) +#else +# define OIIO_HARDENING_ASSERT_FAST_MSG(...) (void)0 +#endif + +#if OIIO_HARDENING_DEFAULT >= OIIO_HARDENING_EXTENSIVE +# define OIIO_HARDENING_ASSERT_EXTENSIVE_MSG(condition, message) \ + OIIO_CONTRACT_ASSERT_MSG(condition, message) +#else +# define OIIO_HARDENING_ASSERT_EXTENSIVE_MSG(...) (void)0 +#endif + +#if OIIO_HARDENING_DEFAULT >= OIIO_HARDENING_DEBUG +# define OIIO_HARDENING_ASSERT_DEBUG_MSG(condition, message) \ + OIIO_CONTRACT_ASSERT_MSG(condition, message) +#else +# define OIIO_HARDENING_ASSERT_DEBUG_MSG(...) (void)0 +#endif + +#define OIIO_HARDENING_ASSERT_NONE(condition) \ + OIIO_HARDENING_ASSERT_NONE_MSG(condition, #condition) +#define OIIO_HARDENING_ASSERT_FAST(condition) \ + OIIO_HARDENING_ASSERT_FAST_MSG(condition, #condition) +#define OIIO_HARDENING_ASSERT_EXTENSIVE(condition) \ + OIIO_HARDENING_ASSERT_EXTENSIVE_MSG(condition, #condition) +#define OIIO_HARDENING_ASSERT_DEBUG(condition) \ + OIIO_HARDENING_ASSERT_DEBUG_MSG(condition, #condition) + + +OIIO_NAMESPACE_3_1_BEGIN +// Internal contract assertion handler +OIIO_UTIL_API void +contract_violation_handler(const char* location, const char* function, + const char* msg = ""); +OIIO_NAMESPACE_3_1_END + +OIIO_NAMESPACE_BEGIN +#ifndef OIIO_DOXYGEN +using v3_1::contract_violation_handler; +#endif +OIIO_NAMESPACE_END + + /// OIIO_ABORT_IF_DEBUG is a call to abort() for debug builds, but does /// nothing for release builds. #ifndef NDEBUG diff --git a/src/include/OpenImageIO/detail/fmt.h b/src/include/OpenImageIO/detail/fmt.h index b68ea14e02..3558152ae7 100644 --- a/src/include/OpenImageIO/detail/fmt.h +++ b/src/include/OpenImageIO/detail/fmt.h @@ -70,12 +70,9 @@ OIIO_PRAGMA_WARNING_PUSH OIIO_PRAGMA_WARNING_POP -// At some point a method signature changed -#if FMT_VERSION >= 90000 -# define OIIO_FMT_CUSTOM_FORMATTER_CONST const -#else -# define OIIO_FMT_CUSTOM_FORMATTER_CONST -#endif +// DEPRECATED(3.2): This definition is obsolete and should be removed at the +// next ABI compatibility boundary. +#define OIIO_FMT_CUSTOM_FORMATTER_CONST const OIIO_NAMESPACE_3_1_BEGIN @@ -132,18 +129,14 @@ template - auto format(const T& v, FormatContext& ctx) OIIO_FMT_CUSTOM_FORMATTER_CONST + auto format(const T& v, FormatContext& ctx) const { std::string vspec = elem_fmt.size() ? fmt::format("{{:{}}}", elem_fmt) : std::string("{}"); for (size_t i = 0; i < size_t(v.size()); ++i) { if (i) fmt::format_to(ctx.out(), "{}", sep == ',' ? ", " : " "); -#if FMT_VERSION >= 80000 fmt::format_to(ctx.out(), fmt::runtime(vspec), v[i]); -#else - fmt::format_to(ctx.out(), vspec, v[i]); -#endif } return ctx.out(); } @@ -177,19 +170,15 @@ template struct array_formatter : format_parser_with_separator { // inherits parse() from format_parser_with_separator template - auto format(const T& v, FormatContext& ctx) OIIO_FMT_CUSTOM_FORMATTER_CONST + auto format(const T& v, FormatContext& ctx) const { std::string vspec = elem_fmt.size() ? fmt::format("{{:{}}}", elem_fmt) : std::string("{}"); for (int i = 0; i < Size; ++i) { if (i) fmt::format_to(ctx.out(), "{}", sep == ',' ? ", " : " "); -#if FMT_VERSION >= 80000 fmt::format_to(ctx.out(), fmt::runtime(vspec), ((const Elem*)&v)[i]); -#else - fmt::format_to(ctx.out(), vspec, ((const Elem*)&v)[i]); -#endif } return ctx.out(); } diff --git a/src/include/OpenImageIO/fmath.h b/src/include/OpenImageIO/fmath.h index bb73ff14ea..ea31eb43ce 100644 --- a/src/include/OpenImageIO/fmath.h +++ b/src/include/OpenImageIO/fmath.h @@ -144,7 +144,11 @@ ispow2(T x) noexcept // Numerous references for this bit trick are on the web. The // principle is that x is a power of 2 <=> x == 1< x-1 is // all 1 bits for bits < b. - return (x & (x - 1)) == 0 && (x >= 0); + if constexpr (std::is_signed::value) { + return (x & (x - 1)) == 0 && (x >= 0); + } else { + return (x & (x - 1)) == 0; + } } diff --git a/src/include/OpenImageIO/hash.h b/src/include/OpenImageIO/hash.h index 8424869a97..9005040b86 100644 --- a/src/include/OpenImageIO/hash.h +++ b/src/include/OpenImageIO/hash.h @@ -247,8 +247,11 @@ strhash (string_view s) size_t len = s.length(); if (! len) return 0; unsigned int h = 0; - for (size_t i = 0; i < len; ++i) { - h += (unsigned char)(s[i]); + for (auto c : s) { + // Note: by using range for here, instead of looping over indices and + // calling operator[] to get each char, we avoid the bounds checking + // that operator[] does. + h += (unsigned char)(c); h += h << 10; h ^= h >> 6; } diff --git a/src/include/OpenImageIO/image_span.h b/src/include/OpenImageIO/image_span.h index 2684c15504..3cea3215ee 100644 --- a/src/include/OpenImageIO/image_span.h +++ b/src/include/OpenImageIO/image_span.h @@ -271,18 +271,27 @@ template class image_span { /// Return a pointer to the value at channel c, pixel (x,y,z). inline T* getptr(int c, int x, int y, int z = 0) const { - // Bounds check in debug mode - OIIO_DASSERT(unsigned(c) < unsigned(nchannels()) - && unsigned(x) < unsigned(width()) - && unsigned(y) < unsigned(height()) - && unsigned(z) < unsigned(depth())); if constexpr (Rank == 2) { OIIO_DASSERT(y == 0 && z == 0); + OIIO_CONTRACT_ASSERT(unsigned(c) < unsigned(nchannels()) + && unsigned(x) < unsigned(width())); + return (T*)((char*)data() + c * chanstride()); } else if constexpr (Rank == 3) { OIIO_DASSERT(z == 0); + OIIO_CONTRACT_ASSERT(unsigned(c) < unsigned(nchannels()) + && unsigned(x) < unsigned(width()) + && unsigned(y) < unsigned(height())); + return (T*)((char*)data() + c * chanstride() + x * xstride() + + y * ystride()); + } else { + // Rank == 4 + OIIO_CONTRACT_ASSERT(unsigned(c) < unsigned(nchannels()) + && unsigned(x) < unsigned(width()) + && unsigned(y) < unsigned(height()) + && unsigned(z) < unsigned(depth())); + return (T*)((char*)data() + c * chanstride() + x * xstride() + + y * ystride() + z * zstride()); } - return (T*)((char*)data() + c * chanstride() + x * xstride() - + y * ystride() + z * zstride()); } /// Return a pointer to the value at channel 0, pixel (x,y,z). diff --git a/src/include/OpenImageIO/imagebuf.h b/src/include/OpenImageIO/imagebuf.h index adc6709d48..eb0227688d 100644 --- a/src/include/OpenImageIO/imagebuf.h +++ b/src/include/OpenImageIO/imagebuf.h @@ -1369,6 +1369,16 @@ class OIIO_API ImageBuf { void* localpixels(); const void* localpixels() const; + /// Return an `image_span` giving the extent and layout + /// of "local" pixel memory, if they are fully in RAM and not backed by an + /// ImageCache, or an empty span otherwise. + image_span localpixels_as_byte_image_span() const; + + /// Return an `image_span` giving the extent and layout of + /// "local" pixel memory, if they are fully in RAM and not backed by an + /// ImageCache, and it is a writable IB, or an empty span otherwise. + image_span localpixels_as_writable_byte_image_span(); + /// Pixel-to-pixel stride within the localpixels memory. stride_t pixel_stride() const; /// Scanline-to-scanline stride within the localpixels memory. diff --git a/src/include/OpenImageIO/imagebufalgo.h b/src/include/OpenImageIO/imagebufalgo.h index 4e9c80fcdc..4bce5c1b6e 100644 --- a/src/include/OpenImageIO/imagebufalgo.h +++ b/src/include/OpenImageIO/imagebufalgo.h @@ -2247,6 +2247,7 @@ enum MakeTextureMode { /// the coordinates for normal maps. ("") /// - `maketx:verbose` (int) : How much detail should go to outstream (0). /// - `maketx:runstats` (int) : If nonzero, print run stats to outstream (0). +/// - `maketx:threads` (int) : Number of threads to use (0 = auto). /// - `maketx:resize` (int) : If nonzero, resize to power of 2. (0) /// - `maketx:keepaspect` (int): If nonzero, save aspect ratio to metadata. (0) /// - `maketx:nomipmap` (int) : If nonzero, only output the top MIP level (0). diff --git a/src/include/OpenImageIO/imageio.h b/src/include/OpenImageIO/imageio.h index e6c1ab0552..4b9a05304b 100644 --- a/src/include/OpenImageIO/imageio.h +++ b/src/include/OpenImageIO/imageio.h @@ -3697,18 +3697,28 @@ OIIO_API bool has_error(); /// error messages. OIIO_API std::string geterror(bool clear = true); -/// `OIIO::attribute()` sets a global attribute (i.e., a property or -/// option) of OpenImageIO. The `name` designates the name of the attribute, -/// `type` describes the type of data, and `value` is a pointer to memory -/// containing the new value for the attribute. +/// @defgroup OIIO_attribute (global OIIO::attribute()) +/// @{ /// -/// If the name is known, valid attribute that matches the type specified, -/// the attribute will be set to the new value and `attribute()` will return -/// `true`. If `name` is not recognized, or if the types do not match -/// (e.g., `type` is `TypeFloat` but the named attribute is a string), the -/// attribute will not be modified, and `attribute()` will return `false`. +/// `OIIO::attribute()` sets a global attribute (i.e., a property or option) +/// of OpenImageIO. The `name` designates the name of the attribute, `value` +/// is the value to use for the attribute, and for some varieties of the call, +/// `type` is a TypeDesc describing the data type. /// -/// The following are the recognized attributes: +/// Most varieties of the call will return `true` if `name` is a known +/// attribute and its expected type is compatible with the type specified. If +/// `name` is not recognized, or if the types do not match (e.g., `type` is +/// `TypeFloat` but the named attribute is supposed to be a string), the +/// internal attribute will not be modified, and `attribute()` will return +/// `false`. +/// +/// In all cases, is up to the caller to ensure that `value` is or refers to +/// the right kind and size of storage for the given type. +/// +/// Note that all attributes set by this call may also be retrieved by +/// `OIIO::getattribute()`. +/// +/// RECOGNIZED ATTRIBUTES /// /// - `string options` /// @@ -3927,7 +3937,25 @@ OIIO_API std::string geterror(bool clear = true); /// enable globally in an environment where security is a higher priority /// than being tolerant of partially broken image files. /// -/// @version 3.1 +/// EXAMPLES: +/// ``` +/// // Setting single simple values simply: +/// bool ok = OIIO::getattribute("threads", 1); // implied: int +/// ok = OIIO::attribute("plugin_searchpath", "/foo/bar:/baz"); // implied: string +/// +/// // Setting a more complex value using a span, with explicit type +/// float missing[4] = { 0.0f, 0.0f, 0.0f, 1.0f }; +/// ok = OIIO::attribute("missingcolor", TypeDesc("float[4]"), make_span(missing)); +/// ``` +/// +/// The different varieties of `OIIO::attribute()` call follow: + +/// Set the attribute's value from a span (which may be a single value). The +/// total size of `value` must match the `type` (if not, an assertion will be +/// thrown for debug builds of OIIO, an error will be printed for release +/// builds). +/// +/// @version 3.1+ template inline bool attribute(string_view name, TypeDesc type, span value) { @@ -3936,19 +3964,18 @@ inline bool attribute(string_view name, TypeDesc type, span value) return attribute(name, type, OIIO::as_bytes(value)); } -/// A version of `OIIO::attribute()` that takes its value from a span of -/// untyped bytes. The total size of `value` must match the `type` (if not, an -/// assertion will be thrown for debug builds of OIIO, an error will be -/// printed for release builds). +/// Set the attribute's value from a span of untyped bytes. The total size of +/// `value` must match the `type` (if not, an assertion will be thrown for +/// debug builds of OIIO, an error will be printed for release builds). /// -/// @version 3.1 +/// @version 3.1+ OIIO_API bool attribute(string_view name, TypeDesc type, cspan value); -/// A version of `OIIO::attribute()` where the `value` is only a pointer -/// specifying the beginning of the memory where the value should be copied -/// from. This is "unsafe" in the sense that there is no assurance that it -/// points to a sufficient amount of memory, so the span-based versions of -/// `attribute()` are preferred. +/// Set the named attribute to the contents of memory pointed to by `value`, +/// with the `type` implying the total size to be copied. This is "unsafe" in +/// the sense that there is no assurance that it points to a sufficient amount +/// of memory or value type, so the span-based versions of `attribute()` are +/// preferred. /// /// This was added in version 2.1. OIIO_API bool attribute(string_view name, TypeDesc type, const void* value); @@ -3967,12 +3994,23 @@ inline bool attribute(string_view name, string_view value) { const char *s = valstr.c_str(); return attribute(name, TypeString, &s); } +/// @} + -/// Get the named global attribute of OpenImageIO, store it in `value`. -/// Return `true` if found and it was compatible with the type specified, -/// otherwise return `false` and do not modify the contents of `value`. It -/// is up to the caller to ensure that `val` points to the right kind and -/// size of storage for the given type. +/// @defgroup OIIO_getattribute (global OIIO::getattribute()) +/// @{ +/// +/// `OIIO::getattribute()` retrieves a named global attribute of OpenImageIO, +/// and stores it in `value`. These are the retrieval side of the symmetric +/// set of `OIIO::attribute()` calls. +/// +/// Most varieties of the call will return `true` if the named attribute was +/// found and it was compatible with the type specified, otherwise return +/// `false` and do not modify the contents of `value`. In all cases, it is up +/// to the caller to ensure that `val` points to the right kind and size of +/// storage for the given type. +/// +/// RECOGNIZED ATTRIBUTES /// /// In addition to being able to retrieve all the attributes that are /// documented as settable by the `OIIO::attribute()` call, `getattribute()` @@ -4104,8 +4142,32 @@ inline bool attribute(string_view name, string_view value) { /// IBA::resize 20 0.24s (avg 12.18ms) /// IBA::zero 8 0.66ms (avg 0.08ms) /// +/// EXAMPLES: +/// ``` +/// // Retrieving a single simple value with success/failure return: +/// int threads; +/// bool ok = OIIO::getattribute("threads", threads); +/// std::string path; +/// ok = OIIO::getattribute("plugin_searchpath", path); /// -/// @version 3.1 +/// // Directly returning a single simple value, with default to use +/// // if the attribute is not found: +/// int threads = OIIO::get_int_attribute("threads", 0); +/// string_view path = OIIO::get_string_attribute("plugin_searchpath"); +/// +/// // Returning into a span, with explicit type +/// float missing[4]; +/// ok = OIIO::getattribute("missingcolor", TypeDesc("float[4]"), +/// make_span(missing)); +/// ``` +/// +/// The different varieties of `OIIO::getattribute()` call follow: + +/// Store the named attribute's current value into a writable span. The total +/// size of `value` must match the `type` (if not, an assertion will be thrown +/// for debug OIIO builds, an error will be printed for release builds). +/// +/// @version 3.1+ template inline bool getattribute(string_view name, TypeDesc type, span value) { @@ -4114,37 +4176,37 @@ inline bool getattribute(string_view name, TypeDesc type, span value) return OIIO::v3_1::getattribute(name, type, OIIO::as_writable_bytes(value)); } -/// A version of `getattribute()` that stores the value in a span of -/// untyped bytes. The total size of `value` must match the `type` (if -/// not, an assertion will be thrown for debug OIIO builds, an error will -/// be printed for release builds). +/// Store the value in a span of untyped bytes. The total size of `value` must +/// match the `type` (if not, an assertion will be thrown for debug OIIO +/// builds, an error will be printed for release builds). /// -/// @version 3.1 +/// @version 3.1+ OIIO_API bool getattribute(string_view name, TypeDesc type, span value); -/// A version of `OIIO::getattribute()` where the `value` is only a pointer -/// specifying the beginning of the memory where the value should be copied. -/// This is "unsafe" in the sense that there is no assurance that it points to -/// a sufficient amount of memory, so the span-based versions of `attribute()` -/// are preferred. +/// Store the value into memory pointed to by `val`. This is "unsafe" in the +/// sense that there is no assurance that it points to a sufficient amount of +/// memory or will be interpreted as the correct type, so the span-based +/// versions of `attribute()` are preferred. OIIO_API bool getattribute(string_view name, TypeDesc type, void* val); -/// Shortcut getattribute() for retrieving a single integer. The value is -/// placed in `value`, and the function returns `true` if the attribute was -/// found and was legally convertible to an int. +/// Retrieve a single-integer attribute. The value is placed in `value`, and +/// the function returns `true` if the attribute was found and was legally +/// convertible to an int. inline bool getattribute (string_view name, int &value) { return getattribute (name, TypeInt, &value); } -/// Shortcut getattribute() for retrieving a single float. The value is placed -/// in `value`, and the function returns `true` if the attribute was found and -/// was legally convertible to a float. + +/// Retrieve a single-float attribute. The value is placed in `value`, and the +/// function returns `true` if the attribute was found and was legally +/// convertible to a float. inline bool getattribute (string_view name, float &value) { return getattribute (name, TypeFloat, &value); } -/// Shortcut getattribute() for retrieving a single string as a `std::string`. -/// The value is placed in `value`, and the function returns `true` if the -/// attribute was found. + +/// Retrieve a single-string attribute, placed as a `std::string` into +/// `value`, and the function returns `true` if the attribute was found and +/// was legally convertible to an string. inline bool getattribute (string_view name, std::string &value) { ustring s; bool ok = getattribute (name, TypeString, &s); @@ -4152,32 +4214,44 @@ inline bool getattribute (string_view name, std::string &value) { value = s.string(); return ok; } -/// Shortcut getattribute() for retrieving a single string as a `char*`. -inline bool getattribute (string_view name, char **val) { - return getattribute (name, TypeString, val); + +/// Retrieve a single-string attribute, placed as a `const char*` into +/// `*value`, and the function returns `true` if the attribute was found and +/// was legally convertible to an string. Note that the `const char*` +/// retrieved is really the characters belonging to a `ustring`, and so is +/// owned by OIIO and should not be freed by the calling code. +inline bool getattribute (string_view name, char **value) { + return getattribute (name, TypeString, value); } -/// Shortcut getattribute() for retrieving a single integer, with a supplied -/// default value that will be returned if the attribute is not found or -/// could not legally be converted to an int. + +/// Retrieve a single-integer attribute, with a supplied default value that +/// will be returned if the attribute is not found or could not legally be +/// converted to an int. inline int get_int_attribute (string_view name, int defaultval=0) { int val; return getattribute (name, TypeInt, &val) ? val : defaultval; } -/// Shortcut getattribute() for retrieving a single float, with a supplied -/// default value that will be returned if the attribute is not found or -/// could not legally be converted to a float. + +/// Retrieve a single-float attribute, with a supplied default value that +/// will be returned if the attribute is not found or could not legally be +/// converted to a float. inline float get_float_attribute (string_view name, float defaultval=0) { float val; return getattribute (name, TypeFloat, &val) ? val : defaultval; } -/// Shortcut getattribute() for retrieving a single string, with a supplied -/// default value that will be returned if the attribute is not found. + +/// Retrieve a single-string attribute, with a supplied default value that +/// will be returned if the attribute is not found or could not legally be +/// converted to an int. default value that will be returned if the attribute +/// is not found. inline string_view get_string_attribute (string_view name, string_view defaultval = string_view()) { ustring val; return getattribute (name, TypeString, &val) ? string_view(val) : defaultval; } +/// @} + /// Set the metadata of the `spec` to presume that color space is `name` (or /// to assume nothing about the color space if `name` is empty). The core diff --git a/src/include/OpenImageIO/platform.h b/src/include/OpenImageIO/platform.h index e4760e5545..a2b134da4b 100644 --- a/src/include/OpenImageIO/platform.h +++ b/src/include/OpenImageIO/platform.h @@ -305,7 +305,12 @@ /// enough to cause trouble). Consider using the OIIO_ALLOCATE_STACK_OR_HEAP /// idiom rather than a direct OIIO_ALLOCA if you aren't sure the item will /// be small. -#if defined(__GNUC__) +#if defined(__has_include) +# if __has_include() +# include // for alloca (when available) +# endif +#endif +#if defined(__GNUC__) || defined(__clang__) # define OIIO_ALLOCA(type, size) (assert(size < (1<<20)), (size) != 0 ? ((type*)__builtin_alloca((size) * sizeof(type))) : nullptr) #else # define OIIO_ALLOCA(type, size) (assert(size < (1<<20)), (size) != 0 ? ((type*)alloca((size) * sizeof(type))) : nullptr) @@ -340,8 +345,6 @@ # define OIIO_ALIGN(size) __attribute__((aligned(size))) #elif defined(_MSC_VER) # define OIIO_ALIGN(size) __declspec(align(size)) -#elif defined(__INTEL_COMPILER) -# define OIIO_ALIGN(size) __declspec(align((size))) #else # define OIIO_ALIGN(size) alignas(size) #endif @@ -365,7 +368,7 @@ // if (OIIO_UNLIKELY(x)) ... // if you think x will rarely be true // Caveat: Programmers are notoriously bad at guessing this, so it // should be used only with thorough benchmarking. -#if defined(__GNUC__) || defined(__clang__) || defined(__INTEL_COMPILER) +#if defined(__GNUC__) || defined(__clang__) # define OIIO_LIKELY(x) (__builtin_expect(bool(x), true)) # define OIIO_UNLIKELY(x) (__builtin_expect(bool(x), false)) #else @@ -382,7 +385,7 @@ # define OIIO_FORCEINLINE __inline__ #elif defined(__GNUC__) || defined(__clang__) || __has_attribute(always_inline) # define OIIO_FORCEINLINE inline __attribute__((always_inline)) -#elif defined(_MSC_VER) || defined(__INTEL_COMPILER) +#elif defined(_MSC_VER) # define OIIO_FORCEINLINE __forceinline #else # define OIIO_FORCEINLINE inline @@ -394,7 +397,7 @@ // optimizations by knowing that calling the function cannot possibly alter // any other memory. This declaration goes after the function declaration: // int blah (int arg) OIIO_PURE_FUNC; -#if defined(__GNUC__) || defined(__clang__) || defined(__INTEL_COMPILER) || __has_attribute(pure) +#if defined(__GNUC__) || defined(__clang__) || __has_attribute(pure) # define OIIO_PURE_FUNC __attribute__((pure)) #elif defined(_MSC_VER) # define OIIO_PURE_FUNC /* seems not supported by MSVS */ @@ -408,7 +411,7 @@ // no side effects. This is even more strict than 'pure', and allows even // more optimizations (such as eliminating multiple calls to the function // that have the exact same argument values). -#if defined(__GNUC__) || defined(__clang__) || defined(__INTEL_COMPILER) || __has_attribute(const) +#if defined(__GNUC__) || defined(__clang__) || __has_attribute(const) # define OIIO_CONST_FUNC __attribute__((const)) #elif defined(_MSC_VER) # define OIIO_CONST_FUNC /* seems not supported by MSVS */ @@ -425,7 +428,7 @@ // OIIO_RESTRICT is a parameter attribute that indicates a promise that the // parameter definitely will not alias any other parameters in such a way // that creates a data dependency. Use with caution! -#if defined(__GNUC__) || defined(__clang__) || defined(_MSC_VER) || defined(__INTEL_COMPILER) +#if defined(__GNUC__) || defined(__clang__) || defined(_MSC_VER) # define OIIO_RESTRICT __restrict #else # define OIIO_RESTRICT @@ -469,7 +472,7 @@ // false positives that you can't easily get rid of. // This should work for any clang >= 3.3 and gcc >= 4.8, which are // guaranteed by our minimum requirements. -#if defined(__clang__) || (OIIO_GNUC_VERSION > 90000 && !defined(__INTEL_COMPILER)) \ +#if defined(__clang__) || OIIO_GNUC_VERSION > 90000 \ || __has_attribute(no_sanitize_address) # define OIIO_NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address)) #else @@ -480,8 +483,7 @@ // OIIO_NO_SANITIZE_UNDEFINED can be used to mark a function that you don't // want undefined behavior sanitizer to catch. Only use this if you know there // are false positives that you can't easily get rid of. -#if defined(__clang__) || (OIIO_GNUC_VERSION > 90000 && !defined(__INTEL_COMPILER)) \ - || __has_attribute(no_sanitize) +#if defined(__clang__) || OIIO_GNUC_VERSION > 90000 || __has_attribute(no_sanitize) # define OIIO_NO_SANITIZE_UNDEFINED __attribute__((no_sanitize("undefined"))) #else # define OIIO_NO_SANITIZE_UNDEFINED @@ -623,10 +625,7 @@ template inline T* aligned_new(Args&&... args) { static_assert(alignof(T) > alignof(void*), "Type doesn't seem to be over-aligned, aligned_new is not required"); void* ptr = aligned_malloc(sizeof(T), alignof(T)); - OIIO_PRAGMA_WARNING_PUSH - OIIO_INTEL_PRAGMA(warning disable 873) return ptr ? new (ptr) T(std::forward(args)...) : nullptr; - OIIO_PRAGMA_WARNING_POP } template diff --git a/src/include/OpenImageIO/simd.h b/src/include/OpenImageIO/simd.h index 9bb0b97093..37d8476729 100644 --- a/src/include/OpenImageIO/simd.h +++ b/src/include/OpenImageIO/simd.h @@ -1020,12 +1020,7 @@ class vint4 { vint4& operator=(int a) { load(a); return *this; } /// Assignment from another vint4 -#if !defined(__INTEL_COMPILER) vint4& operator=(const vint4& other) = default; -#else - // For explanation of the necessity of this, see implementation comment. - vint4& operator=(const vint4& other); -#endif /// Component access (get) int operator[] (int i) const; @@ -1314,12 +1309,7 @@ class vint8 { vint8& operator=(int a) { load(a); return *this; } /// Assignment from another vint8 -#if !defined(__INTEL_COMPILER) vint8& operator=(const vint8& other) = default; -#else - // For explanation of the necessity of this, see implementation comment. - vint8& operator=(const vint8& other); -#endif /// Component access (get) int operator[] (int i) const; @@ -1614,12 +1604,7 @@ class vint16 { vint16& operator=(int a) { load(a); return *this; } /// Assignment from another vint16 -#if !defined(__INTEL_COMPILER) vint16& operator=(const vint16& other) = default; -#else - // For explanation of the necessity of this, see implementation comment. - vint16& operator=(const vint16& other); -#endif /// Component access (get) int operator[] (int i) const; @@ -4106,18 +4091,6 @@ OIIO_FORCEINLINE bool none (const vbool16& v) { return reduce_or(v) == false; } ////////////////////////////////////////////////////////////////////// // vint4 implementation -#if defined(__INTEL_COMPILER) -// For reasons we don't understand, all sorts of failures crop up only on icc -// if we make this =default. Although we still support icc for now, it's a -// discontinued compiler, so we special-case it here rather than spend a lot -// of time investigating what might be broken (and would of course never be -// fixed if it's a compiler bug). -OIIO_FORCEINLINE vint4& vint4::operator=(const vint4& other) { - m_simd = other.m_simd; - return *this; -} -#endif - OIIO_FORCEINLINE int vint4::operator[] (int i) const { OIIO_DASSERT(i struct fmt::formatter : OIIO::pvt::array_formatter {}; -// Allow C++ metaprogramming to understand that the simd types are trivially -// copyable (i.e. memcpy to copy simd types is fine). -#if defined(__INTEL_COMPILER) -// Necessary because we have to define the vint types copy constructors on icc -template<> struct std::is_trivially_copyable : std::true_type {}; -template<> struct std::is_trivially_copyable : std::true_type {}; -template<> struct std::is_trivially_copyable : std::true_type {}; -#endif - #undef SIMD_DO #undef SIMD_CONSTRUCT diff --git a/src/include/OpenImageIO/span.h b/src/include/OpenImageIO/span.h index f1c49dafdc..b184809a75 100644 --- a/src/include/OpenImageIO/span.h +++ b/src/include/OpenImageIO/span.h @@ -207,28 +207,28 @@ class span { /// optimized builds, there is no bounds check. Note: this is different /// from C++ std::span, which never bounds checks `operator[]`. constexpr reference operator[] (size_type idx) const { - OIIO_DASSERT(idx < m_size && "OIIO::span::operator[] range check"); + OIIO_CONTRACT_ASSERT(idx < m_size); return m_data[idx]; } constexpr reference operator() (size_type idx) const { - OIIO_DASSERT(idx < m_size && "OIIO::span::operator() range check"); + OIIO_CONTRACT_ASSERT(idx < m_size); return m_data[idx]; } /// Bounds-checked access, throws an assertion if out of range. reference at (size_type idx) const { if (idx >= size()) - throw (std::out_of_range ("OpenImageIO::span::at")); + throw (std::out_of_range ("OIIO::span::at")); return m_data[idx]; } /// The first element of the span. constexpr reference front() const noexcept { - OIIO_DASSERT(m_size >= 1); + OIIO_CONTRACT_ASSERT(m_size >= 1); return m_data[0]; } /// The last element of the span. constexpr reference back() const noexcept { - OIIO_DASSERT(m_size >= 1); + OIIO_CONTRACT_ASSERT(m_size >= 1); return m_data[size() - 1]; } @@ -374,14 +374,16 @@ class span_strided { constexpr stride_type stride() const noexcept { return m_stride; } constexpr reference operator[] (size_type idx) const { + OIIO_CONTRACT_ASSERT(idx < m_size); return m_data[m_stride*idx]; } constexpr reference operator() (size_type idx) const { + OIIO_CONTRACT_ASSERT(idx < m_size); return m_data[m_stride*idx]; } reference at (size_type idx) const { if (idx >= size()) - throw (std::out_of_range ("OpenImageIO::span_strided::at")); + throw (std::out_of_range ("OIIO::span_strided::at")); return m_data[m_stride*idx]; } constexpr reference front() const noexcept { return m_data[0]; } diff --git a/src/include/OpenImageIO/string_view.h b/src/include/OpenImageIO/string_view.h index b959bb9d5d..d07854ceb6 100644 --- a/src/include/OpenImageIO/string_view.h +++ b/src/include/OpenImageIO/string_view.h @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -205,11 +206,12 @@ class basic_string_view { /// Is the view empty, containing no characters? constexpr bool empty() const noexcept { return m_len == 0; } - /// Element access of an individual character. For debug build, does - /// bounds check with assertion. For optimized builds, there is no bounds - /// check. Note: this is different from C++ std::span, which never bounds - /// checks `operator[]`. - constexpr const_reference operator[](size_type pos) const { return m_chars[pos]; } + /// Element access of an individual character. For debug or hardened + /// builds, does bounds check with assertion. + constexpr const_reference operator[](size_type pos) const { + OIIO_CONTRACT_ASSERT(pos < m_len); + return m_chars[pos]; + } /// Element access with bounds checking and exception if out of bounds. constexpr const_reference at(size_t pos) const { @@ -218,9 +220,15 @@ class basic_string_view { return m_chars[pos]; } /// The first character of the view. - constexpr const_reference front() const { return m_chars[0]; } + constexpr const_reference front() const { + OIIO_CONTRACT_ASSERT(m_len >= 1); + return m_chars[0]; + } /// The last character of the view. - constexpr const_reference back() const { return m_chars[m_len - 1]; } + constexpr const_reference back() const { + OIIO_CONTRACT_ASSERT(m_len >= 1); + return m_chars[m_len - 1]; + } /// Return the underlying data pointer to the first character. constexpr const_pointer data() const noexcept { return m_chars; } diff --git a/src/include/OpenImageIO/strutil.h b/src/include/OpenImageIO/strutil.h index c446c49689..f06518d78f 100644 --- a/src/include/OpenImageIO/strutil.h +++ b/src/include/OpenImageIO/strutil.h @@ -204,7 +204,6 @@ namespace sync { /// Output is fully thread-safe (the outputs are "atomic" to the file or /// stream), and if the stream is buffered, it is flushed after the output). -#if FMT_VERSION >= 70000 template inline void print (FILE *file, const Str& fmt, Args&&... args) { @@ -223,26 +222,6 @@ inline void print (std::ostream &file, const Str& fmt, Args&&... args) sync_output (file, ::fmt::vformat(fmt, ::fmt::make_format_args(args...))); } -#else - -template -inline void print (FILE *file, const char* fmt, Args&&... args) -{ - sync_output (file, ::fmt::format(fmt, std::forward(args)...)); -} - -template -inline void print (const char* fmt, Args&&... args) -{ - print(stdout, fmt, std::forward(args)...); -} - -template -inline void print (std::ostream &file, const char* fmt, Args&&... args) -{ - sync_output (file, ::fmt::format(fmt, std::forward(args)...)); -} -#endif } // namespace sync @@ -275,7 +254,7 @@ void print (FILE *file, const char* fmt, const Args&... args); template void print (std::ostream &file, const char* fmt, const Args&... args); -#elif FMT_VERSION >= 70000 && !OIIO_PRINT_IS_SYNCHRONIZED +#elif !OIIO_PRINT_IS_SYNCHRONIZED using ::fmt::print; #else using sync::print; diff --git a/src/include/OpenImageIO/typedesc.h b/src/include/OpenImageIO/typedesc.h index 33cf913560..94eee60633 100644 --- a/src/include/OpenImageIO/typedesc.h +++ b/src/include/OpenImageIO/typedesc.h @@ -385,6 +385,7 @@ inline constexpr TypeDesc TypeHalf (TypeDesc::HALF); inline constexpr TypeDesc TypeTimeCode (TypeDesc::UINT, TypeDesc::SCALAR, TypeDesc::TIMECODE, 2); inline constexpr TypeDesc TypeKeyCode (TypeDesc::INT, TypeDesc::SCALAR, TypeDesc::KEYCODE, 7); inline constexpr TypeDesc TypeRational(TypeDesc::INT, TypeDesc::VEC2, TypeDesc::RATIONAL); +inline constexpr TypeDesc TypeURational(TypeDesc::UINT, TypeDesc::VEC2, TypeDesc::RATIONAL); inline constexpr TypeDesc TypePointer(TypeDesc::PTR); inline constexpr TypeDesc TypeUstringhash(TypeDesc::USTRINGHASH); @@ -409,7 +410,7 @@ template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::UINT64; }; template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::INT64; }; template<> struct BaseTypeFromC { static constexpr TypeDesc::BASETYPE value = TypeDesc::INT64; }; -#if defined(__GNUC__) && (ULONG_MAX == 0xffffffffffffffff) && !(defined(__APPLE__) && defined(__MACH__)) || defined(__NetBSD__) +#if defined(__GNUC__) && (ULONG_MAX == 0xffffffffffffffff) && !(defined(__APPLE__) && defined(__MACH__)) && !defined(__OpenBSD__) || defined(__NetBSD__) // Some platforms consider int64_t and long long to be different types, even // though they are actually the same size. static_assert(!std::is_same_v); @@ -648,6 +649,7 @@ using v3_1::TypeHalf; using v3_1::TypeTimeCode; using v3_1::TypeKeyCode; using v3_1::TypeRational; +using v3_1::TypeURational; using v3_1::TypePointer; using v3_1::TypeUstringhash; #endif @@ -679,7 +681,7 @@ struct fmt::formatter { } template - auto format(const OIIO::TypeDesc& t, FormatContext& ctx) OIIO_FMT_CUSTOM_FORMATTER_CONST + auto format(const OIIO::TypeDesc& t, FormatContext& ctx) const { // C++14: auto format(const OIIO::TypeDesc& p, FormatContext& ctx) const { // ctx.out() is an output iterator to write to. diff --git a/src/include/OpenImageIO/ustring.h b/src/include/OpenImageIO/ustring.h index aba90ffdf5..b3e172dea3 100644 --- a/src/include/OpenImageIO/ustring.h +++ b/src/include/OpenImageIO/ustring.h @@ -1146,8 +1146,7 @@ FMT_BEGIN_NAMESPACE template<> struct formatter : formatter { template - auto format(const OIIO::ustring& u, - FormatContext& ctx) OIIO_FMT_CUSTOM_FORMATTER_CONST + auto format(const OIIO::ustring& u, FormatContext& ctx) const { return formatter::format({ u.data(), u.size() }, ctx); @@ -1157,8 +1156,7 @@ template<> struct formatter : formatter { template<> struct formatter : formatter { template - auto format(const OIIO::ustringhash& h, - FormatContext& ctx) OIIO_FMT_CUSTOM_FORMATTER_CONST + auto format(const OIIO::ustringhash& h, FormatContext& ctx) const { OIIO::ustring u(h); return formatter::format({ u.data(), u.size() }, diff --git a/src/include/imageio_pvt.h b/src/include/imageio_pvt.h index 273375cd77..13d5c06140 100644 --- a/src/include/imageio_pvt.h +++ b/src/include/imageio_pvt.h @@ -43,6 +43,7 @@ extern int oiio_log_times; extern int openexr_core; extern int jpeg_com_attributes; extern int png_linear_premult; +extern int enable_hwy; extern int limit_channels; extern int limit_imagesize_MB; extern int imagebuf_print_uncaught_errors; diff --git a/src/iv/imageviewer.cpp b/src/iv/imageviewer.cpp index 12db1aa772..f4a4924a5e 100644 --- a/src/iv/imageviewer.cpp +++ b/src/iv/imageviewer.cpp @@ -520,6 +520,22 @@ ImageViewer::createActions() closeupAvgPixelsBox->setToolTip(closeupAvgPixelsTooltip); closeupAvgPixelsLabel->setToolTip(closeupAvgPixelsTooltip); + + rotateLeftAct = new QAction(tr("&Rotate Left"), this); + rotateLeftAct->setShortcut(tr("Ctrl+Shift+L")); + connect(rotateLeftAct, SIGNAL(triggered()), this, SLOT(rotateLeft())); + + rotateRightAct = new QAction(tr("&Rotate Right"), this); + rotateRightAct->setShortcut(tr("Ctrl+Shift+R")); + connect(rotateRightAct, SIGNAL(triggered()), this, SLOT(rotateRight())); + + flipHorizontalAct = new QAction(tr("&Flip Horizontal"), this); + connect(flipHorizontalAct, SIGNAL(triggered()), this, + SLOT(flipHorizontal())); + + flipVerticalAct = new QAction(tr("&Flip Vertical"), this); + connect(flipVerticalAct, SIGNAL(triggered()), this, SLOT(flipVertical())); + // Connect signals to ensure closeupAvgPixelsBox value is always <= closeupPixelsBox value connect(closeupPixelsBox, QOverload::of(&QSpinBox::valueChanged), [this](int value) { @@ -780,6 +796,11 @@ ImageViewer::createMenus() toolsMenu->addAction(toggleAreaSampleAct); toolsMenu->addMenu(slideMenu); toolsMenu->addMenu(sortMenu); + toolsMenu->addSeparator(); + toolsMenu->addAction(rotateLeftAct); + toolsMenu->addAction(rotateRightAct); + toolsMenu->addAction(flipHorizontalAct); + toolsMenu->addAction(flipVerticalAct); // Menus, toolbars, & status // Annotate @@ -2455,3 +2476,82 @@ ImageViewer::areaSampleMode() const { return m_areaSampleMode; } + + +void +ImageViewer::rotateLeft() +{ + IvImage* img = cur(); + if (!img) + return; + + ImageSpec* spec = curspecmod(); + + int curr_orientation = spec->get_int_attribute("Orientation", 1); + + if (curr_orientation >= 1 && curr_orientation <= 8) { + static int next_orientation[] = { 0, 8, 5, 6, 7, 4, 1, 2, 3 }; + curr_orientation = next_orientation[curr_orientation]; + spec->attribute("Orientation", curr_orientation); + } + displayCurrentImage(); +} + + +void +ImageViewer::rotateRight() +{ + IvImage* img = cur(); + if (!img) + return; + + ImageSpec* spec = curspecmod(); + int curr_orientation = spec->get_int_attribute("Orientation", 1); + + if (curr_orientation >= 1 && curr_orientation <= 8) { + static int next_orientation[] = { 0, 6, 7, 8, 5, 2, 3, 4, 1 }; + curr_orientation = next_orientation[curr_orientation]; + spec->attribute("Orientation", curr_orientation); + } + displayCurrentImage(); +} + + +void +ImageViewer::flipHorizontal() +{ + IvImage* img = cur(); + if (!img) + return; + + ImageSpec* spec = curspecmod(); + + int curr_orientation = spec->get_int_attribute("Orientation", 1); + + if (curr_orientation >= 1 && curr_orientation <= 8) { + static int next_orientation[] = { 0, 2, 1, 4, 3, 6, 5, 8, 7 }; + curr_orientation = next_orientation[curr_orientation]; + spec->attribute("Orientation", curr_orientation); + } + displayCurrentImage(); +} + + +void +ImageViewer::flipVertical() +{ + IvImage* img = cur(); + if (!img) + return; + + ImageSpec* spec = curspecmod(); + + int curr_orientation = spec->get_int_attribute("Orientation", 1); + + if (curr_orientation >= 1 && curr_orientation <= 8) { + static int next_orientation[] = { 0, 4, 3, 2, 1, 8, 7, 6, 5 }; + curr_orientation = next_orientation[curr_orientation]; + spec->attribute("Orientation", curr_orientation); + } + displayCurrentImage(); +} \ No newline at end of file diff --git a/src/iv/imageviewer.h b/src/iv/imageviewer.h index 3a255647bb..763aaf0dd7 100644 --- a/src/iv/imageviewer.h +++ b/src/iv/imageviewer.h @@ -219,6 +219,14 @@ class ImageViewer final : public QMainWindow { return img ? &img->spec() : NULL; } + /// Return a modifiable ref to the current image spec, or NULL if there is no + /// current image. + ImageSpec* curspecmod(void) const + { + IvImage* img = cur(); + return img ? &img->specmod() : NULL; + } + bool pixelviewOn(void) const { return showPixelviewWindowAct && showPixelviewWindowAct->isChecked(); @@ -334,6 +342,11 @@ private slots: void editPreferences(); ///< Edit viewer preferences void toggleAreaSample(); ///< Use area probe + void rotateLeft(); + void rotateRight(); + void flipHorizontal(); + void flipVertical(); + void useOCIOAction(bool checked); void ocioColorSpaceAction(); void ocioDisplayViewAction(); @@ -404,6 +417,8 @@ private slots: QAction* showPixelviewWindowAct; QAction* toggleAreaSampleAct; QAction* toggleWindowGuidesAct; + QAction *rotateLeftAct, *rotateRightAct, *flipHorizontalAct, + *flipVerticalAct; QMenu *fileMenu, *editMenu, /**imageMenu,*/ *viewMenu, *toolsMenu, *helpMenu; QMenu* openRecentMenu; diff --git a/src/jpeg.imageio/jpegoutput.cpp b/src/jpeg.imageio/jpegoutput.cpp index f6058deb25..68156aad0d 100644 --- a/src/jpeg.imageio/jpegoutput.cpp +++ b/src/jpeg.imageio/jpegoutput.cpp @@ -40,9 +40,6 @@ class JpgOutput final : public ImageOutput { OpenMode mode = Create) override; bool write_scanline(int y, int z, TypeDesc format, const void* data, stride_t xstride) override; - bool write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, - stride_t zstride) override; bool close() override; bool copy_image(ImageInput* in) override; @@ -55,7 +52,7 @@ class JpgOutput final : public ImageOutput { struct jpeg_error_mgr c_jerr; jvirt_barray_ptr* m_copy_coeffs; struct jpeg_decompress_struct* m_copy_decompressor; - std::vector m_tilebuffer; + // m_outbuffer/m_outsize are used for jpeg-to-memory unsigned char* m_outbuffer = nullptr; #if OIIO_JPEG_LIB_VERSION >= 94 @@ -356,11 +353,6 @@ JpgOutput::open(const std::string& name, const ImageSpec& newspec, m_dither = m_spec.get_int_attribute("oiio:dither", 0); - // If user asked for tiles -- which JPEG doesn't support, emulate it by - // buffering the whole image. - if (m_spec.tile_width && m_spec.tile_height) - m_tilebuffer.resize(m_spec.image_bytes()); - return true; } @@ -370,9 +362,9 @@ void JpgOutput::resmeta_to_density() { // Clear cruft from Exif that might confuse us - m_spec.erase_attribute("exif:XResolution"); - m_spec.erase_attribute("exif:YResolution"); - m_spec.erase_attribute("exif:ResolutionUnit"); + m_spec.erase_attribute("Exif:XResolution"); + m_spec.erase_attribute("Exif:YResolution"); + m_spec.erase_attribute("Exif:ResolutionUnit"); string_view resunit = m_spec.get_string_attribute("ResolutionUnit"); if (Strutil::iequals(resunit, "none")) @@ -517,17 +509,6 @@ JpgOutput::write_scanline(int y, int z, TypeDesc format, const void* data, -bool -JpgOutput::write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, stride_t zstride) -{ - // Emulate tiles by buffering the whole image - return copy_tile_to_image_buffer(x, y, z, format, data, xstride, ystride, - zstride, &m_tilebuffer[0]); -} - - - bool JpgOutput::close() { @@ -536,16 +517,6 @@ JpgOutput::close() return true; } - bool ok = true; - - if (m_spec.tile_width) { - // We've been emulating tiles; now dump as scanlines. - OIIO_DASSERT(m_tilebuffer.size()); - ok &= write_scanlines(m_spec.y, m_spec.y + m_spec.height, 0, - m_spec.format, &m_tilebuffer[0]); - std::vector().swap(m_tilebuffer); // free it - } - if (m_next_scanline < spec().height && m_copy_coeffs == NULL) { // But if we've only written some scanlines, write the rest to avoid // errors @@ -578,7 +549,7 @@ JpgOutput::close() } init(); - return ok; + return true; } diff --git a/src/jpegxl.imageio/jxlinput.cpp b/src/jpegxl.imageio/jxlinput.cpp index b0f90cf4f1..88f2ccea06 100644 --- a/src/jpegxl.imageio/jxlinput.cpp +++ b/src/jpegxl.imageio/jxlinput.cpp @@ -383,9 +383,15 @@ JxlInput::open(const std::string& name, ImageSpec& newspec) if (have_color_encoding && color_encoding.primaries != JXL_PRIMARIES_CUSTOM && color_encoding.white_point != JXL_WHITE_POINT_CUSTOM && color_encoding.transfer_function != JXL_TRANSFER_FUNCTION_GAMMA) { - const int cicp[4] = { color_encoding.primaries, - color_encoding.transfer_function, 0 /* RGB */, - 1 /* Full range */ }; + int color_primaries = color_encoding.primaries; + // JxlPrimaries enum only covers P3 primaries as value 11 and not 12 + // but CICP has separate code values based on white point. + if (color_primaries == JXL_PRIMARIES_P3 + && color_encoding.white_point == JXL_WHITE_POINT_D65) { + color_primaries = 12; + } + const int cicp[4] = { color_primaries, color_encoding.transfer_function, + 0 /* RGB */, 1 /* Full range */ }; m_spec.attribute("CICP", TypeDesc(TypeDesc::INT, 4), cicp); const ColorConfig& colorconfig(ColorConfig::default_colorconfig()); string_view interop_id = colorconfig.get_color_interop_id(cicp); diff --git a/src/jpegxl.imageio/jxloutput.cpp b/src/jpegxl.imageio/jxloutput.cpp index 1a4a64e858..37fff33817 100644 --- a/src/jpegxl.imageio/jxloutput.cpp +++ b/src/jpegxl.imageio/jxloutput.cpp @@ -571,7 +571,18 @@ JxlOutput::save_image(const void* data) // primaries and white point are not currently used but could help // support more CICP codes. JxlColorEncoding color_encoding {}; - color_encoding.primaries = JxlPrimaries(cicp[0]); + color_encoding.primaries = JxlPrimaries(cicp[0]); + // CICP primaries 11 and 12 both represent P3, but with different white points. + if (cicp[0] == 11) { + color_encoding.white_point = JXL_WHITE_POINT_DCI; + } + // JxlPrimaries enum only covers P3 primaries as value 11 and not 12. + else if (cicp[0] == 12) { + color_encoding.primaries = JXL_PRIMARIES_P3; + color_encoding.white_point = JXL_WHITE_POINT_D65; + } else { + color_encoding.white_point = JXL_WHITE_POINT_D65; + } color_encoding.transfer_function = JxlTransferFunction(cicp[1]); color_encoding.color_space = JXL_COLOR_SPACE_RGB; @@ -581,10 +592,7 @@ JxlOutput::save_image(const void* data) switch (color_encoding.primaries) { case JXL_PRIMARIES_SRGB: case JXL_PRIMARIES_2100: - case JXL_PRIMARIES_P3: - supported_primaries = true; - color_encoding.white_point = JXL_WHITE_POINT_D65; - break; + case JXL_PRIMARIES_P3: supported_primaries = true; break; case JXL_PRIMARIES_CUSTOM: // Not an actual CICP code in JXL break; } diff --git a/src/libOpenImageIO/CMakeLists.txt b/src/libOpenImageIO/CMakeLists.txt index f2459b2d32..262c85f4dd 100644 --- a/src/libOpenImageIO/CMakeLists.txt +++ b/src/libOpenImageIO/CMakeLists.txt @@ -163,11 +163,22 @@ target_link_libraries (OpenImageIO $ $ $ - ${BZIP2_LIBRARIES} + $ ZLIB::ZLIB ${CMAKE_DL_LIBS} ) +# Google Highway (hwy) is an optional optimization dependency. +set (_oiio_use_hwy 0) +if (OIIO_USE_HWY AND TARGET hwy::hwy) + set (_oiio_use_hwy 1) + target_link_libraries (OpenImageIO PRIVATE hwy::hwy) + if (TARGET hwy::hwy_contrib) + target_link_libraries (OpenImageIO PRIVATE hwy::hwy_contrib) + endif () +endif () +target_compile_definitions (OpenImageIO PRIVATE OIIO_USE_HWY=${_oiio_use_hwy}) + if (WIN32) target_link_libraries (OpenImageIO PRIVATE psapi) endif() diff --git a/src/libOpenImageIO/color_ocio.cpp b/src/libOpenImageIO/color_ocio.cpp index a365cd9b48..b458673840 100644 --- a/src/libOpenImageIO/color_ocio.cpp +++ b/src/libOpenImageIO/color_ocio.cpp @@ -2803,21 +2803,25 @@ ColorConfig::set_colorspace(ImageSpec& spec, string_view colorspace) const void ColorConfig::set_colorspace_rec709_gamma(ImageSpec& spec, float gamma) const { - gamma = std::round(gamma * 100.0f) / 100.0f; - if (fabsf(gamma - 1.0f) <= 0.01f) { + // Round gamma to the nearest hundredth to prevent stupid precision choices + // and make it easier for apps to make decisions based on known gamma values. + float g_rounded = std::round(gamma * 100.0f) / 100.0f; + if (fabsf(g_rounded - 1.0f) <= 0.01f) { set_colorspace(spec, "lin_rec709_scene"); - } else if (fabsf(gamma - 1.8f) <= 0.01f) { + } else if (fabsf(g_rounded - 1.8f) <= 0.01f) { set_colorspace(spec, "g18_rec709_scene"); spec.attribute("oiio:Gamma", 1.8f); - } else if (fabsf(gamma - 2.2f) <= 0.01f) { + } else if (fabsf(g_rounded - 2.2f) <= 0.01f) { set_colorspace(spec, "g22_rec709_scene"); spec.attribute("oiio:Gamma", 2.2f); - } else if (fabsf(gamma - 2.4f) <= 0.01f) { + } else if (fabsf(g_rounded - 2.4f) <= 0.01f) { set_colorspace(spec, "g24_rec709_scene"); spec.attribute("oiio:Gamma", 2.4f); } else { - set_colorspace(spec, Strutil::fmt::format("g{}_rec709_scene", - std::lround(gamma * 10.0f))); + set_colorspace(spec, + Strutil::fmt::format("g{}_rec709_scene", + std::lround(g_rounded * 10.0f))); + // Preserve the original gamma value for use in color conversions. spec.attribute("oiio:Gamma", gamma); } } diff --git a/src/libOpenImageIO/exif.cpp b/src/libOpenImageIO/exif.cpp index ab95d881ef..c418dec1a0 100644 --- a/src/libOpenImageIO/exif.cpp +++ b/src/libOpenImageIO/exif.cpp @@ -260,13 +260,15 @@ print_dir_entry(std::ostream& out, const TagMap& tagmap, switch (dir.tdir_type) { case TIFF_ASCII: +# ifdef EXIF_TIFF_UTF8 case EXIF_TIFF_UTF8: +# endif OIIO::print(out, "'{}'", string_view(mydata, dir.tdir_count)); break; case TIFF_RATIONAL: { const unsigned int* u = (unsigned int*)mydata; for (size_t i = 0; i < dir.tdir_count; ++i) - OIIO::print(out, "{}/{} = {} ", u[2 * i], << u[2 * i + 1], + OIIO::print(out, "{}/{} = {} ", u[2 * i], u[2 * i + 1], (double)u[2 * i] / (double)u[2 * i + 1]); } break; case TIFF_SRATIONAL: { @@ -449,7 +451,7 @@ static const TagInfo exif_tag_table[] = { { EXIF_SPECTRALSENSITIVITY,"Exif:SpectralSensitivity", TIFF_ASCII, 0 }, { EXIF_ISOSPEEDRATINGS, "Exif:ISOSpeedRatings", TIFF_SHORT, 1 }, { EXIF_OECF, "Exif:OECF", TIFF_NOTYPE, 1 }, // skip it - { EXIF_EXIFVERSION, "Exif:ExifVersion", TIFF_UNDEFINED, 1, version4char_handler }, // skip it + { EXIF_EXIFVERSION, "Exif:ExifVersion", TIFF_UNDEFINED, 1, version4char_handler }, { EXIF_DATETIMEORIGINAL, "Exif:DateTimeOriginal", TIFF_ASCII, 0 }, { EXIF_DATETIMEDIGITIZED,"Exif:DateTimeDigitized", TIFF_ASCII, 0 }, { EXIF_OFFSETTIME,"Exif:OffsetTime", TIFF_ASCII, 0 }, @@ -475,7 +477,7 @@ static const TagInfo exif_tag_table[] = { { EXIF_SUBSECTIME, "Exif:SubsecTime", TIFF_ASCII, 0 }, { EXIF_SUBSECTIMEORIGINAL,"Exif:SubsecTimeOriginal", TIFF_ASCII, 0 }, { EXIF_SUBSECTIMEDIGITIZED,"Exif:SubsecTimeDigitized", TIFF_ASCII, 0 }, - { EXIF_FLASHPIXVERSION, "Exif:FlashPixVersion", TIFF_UNDEFINED, 1, version4char_handler }, // skip "Exif:FlashPixVesion", TIFF_NOTYPE, 1 }, + { EXIF_FLASHPIXVERSION, "Exif:FlashPixVersion", TIFF_UNDEFINED, 1, version4char_handler }, { EXIF_COLORSPACE, "Exif:ColorSpace", TIFF_SHORT, 1 }, { EXIF_PIXELXDIMENSION, "Exif:PixelXDimension", TIFF_LONG, 1 }, { EXIF_PIXELYDIMENSION, "Exif:PixelYDimension", TIFF_LONG, 1 }, @@ -1202,7 +1204,7 @@ decode_exif(cspan exif, ImageSpec& spec) #if DEBUG_EXIF_READ std::cerr << "Exif dump:\n"; - for (size_t i = 0; i < std::min(200L, exif.size()); ++i) { + for (size_t i = 0; i < std::min(200UL, exif.size()); ++i) { if ((i % 16) == 0) std::cerr << "[" << i << "] "; if (exif[i] >= ' ') diff --git a/src/libOpenImageIO/formatspec.cpp b/src/libOpenImageIO/formatspec.cpp index 81374b9624..efd9eff586 100644 --- a/src/libOpenImageIO/formatspec.cpp +++ b/src/libOpenImageIO/formatspec.cpp @@ -41,7 +41,6 @@ inline void get_default_quantize_(long long& quant_min, long long& quant_max) noexcept { OIIO_PRAGMA_WARNING_PUSH - OIIO_INTEL_PRAGMA(warning disable 173) if (std::numeric_limits::is_integer) { quant_min = (long long)std::numeric_limits::min(); quant_max = (long long)std::numeric_limits::max(); diff --git a/src/libOpenImageIO/imagebuf.cpp b/src/libOpenImageIO/imagebuf.cpp index 8a5ed32e0a..fdf14fe447 100644 --- a/src/libOpenImageIO/imagebuf.cpp +++ b/src/libOpenImageIO/imagebuf.cpp @@ -150,10 +150,11 @@ class ImageBufImpl { stride_t ystride = AutoStride, stride_t zstride = AutoStride) { - m_bufspan = image_span(reinterpret_cast(data), - m_spec.nchannels, m_spec.width, m_spec.height, - m_spec.depth, m_spec.format.size(), xstride, - ystride, zstride); + auto formatsize = m_spec.format.size(); + m_bufspan = image_span(reinterpret_cast(data), + m_spec.nchannels, m_spec.width, m_spec.height, + m_spec.depth, formatsize, xstride, ystride, + zstride, formatsize); } bool init_spec(string_view filename, int subimage, int miplevel, @@ -2139,6 +2140,22 @@ ImageBuf::localpixels() +image_span +ImageBuf::localpixels_as_byte_image_span() const +{ + return m_impl->m_bufspan; +} + + + +image_span +ImageBuf::localpixels_as_writable_byte_image_span() +{ + return m_impl->m_readonly ? image_span() : m_impl->m_bufspan; +} + + + const void* ImageBuf::localpixels() const { diff --git a/src/libOpenImageIO/imagebufalgo_addsub.cpp b/src/libOpenImageIO/imagebufalgo_addsub.cpp index c7a4d83e9c..01ba5daa19 100644 --- a/src/libOpenImageIO/imagebufalgo_addsub.cpp +++ b/src/libOpenImageIO/imagebufalgo_addsub.cpp @@ -18,6 +18,10 @@ #include #include +#if defined(OIIO_USE_HWY) && OIIO_USE_HWY +# include "imagebufalgo_hwy_pvt.h" +#endif + #include "imageio_pvt.h" @@ -26,8 +30,8 @@ OIIO_NAMESPACE_3_1_BEGIN template static bool -add_impl(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, ROI roi, - int nthreads) +add_impl_scalar(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, ROI roi, + int nthreads) { ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) { ImageBuf::Iterator r(R, roi); @@ -44,7 +48,8 @@ add_impl(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, ROI roi, template static bool -add_impl(ImageBuf& R, const ImageBuf& A, cspan b, ROI roi, int nthreads) +add_impl_scalar(ImageBuf& R, const ImageBuf& A, cspan b, ROI roi, + int nthreads) { ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) { ImageBuf::Iterator r(R, roi); @@ -58,6 +63,219 @@ add_impl(ImageBuf& R, const ImageBuf& A, cspan b, ROI roi, int nthreads) +#if OIIO_USE_HWY + +// Native integer add using SaturatedAdd (scale-invariant, no float conversion) +template +static bool +add_impl_hwy_native_int(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, + ROI roi, int nthreads) +{ + return hwy_binary_native_int_perpixel_op(R, A, B, roi, nthreads, + [](auto /*d*/, auto a, auto b) { + return hn::SaturatedAdd(a, + b); + }); +} + +template +static bool +add_impl_hwy(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, ROI roi, + int nthreads) +{ + auto op = [](auto /*d*/, auto a, auto b) { return hn::Add(a, b); }; + + // Handle packed RGBA images with an RGB ROI (preserve alpha). + if constexpr (std::is_integral_v && std::is_same_v + && std::is_same_v) { + auto op_int = [](auto /*d*/, auto a, auto b) { + return hn::SaturatedAdd(a, b); + }; + if (hwy_binary_native_int_perpixel_op_rgba_rgb_roi(R, A, B, roi, + nthreads, + op_int)) + return true; + } + if (hwy_binary_perpixel_op_rgba_rgb_roi(R, A, B, roi, + nthreads, op)) + return true; + + return hwy_binary_perpixel_op(R, A, B, roi, nthreads, + op); +} + +template +static bool +add_impl_hwy(ImageBuf& R, const ImageBuf& A, cspan b, ROI roi, + int nthreads) +{ + using SimdType + = std::conditional_t, double, float>; + auto Rv = HwyPixels(R); + auto Av = HwyPixels(A); + ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) { + for (int y = roi.ybegin; y < roi.yend; ++y) { + std::byte* r_row = PixelBase(Rv, roi.xbegin, y); + const std::byte* a_row = PixelBase(Av, roi.xbegin, y); + for (int x = roi.xbegin; x < roi.xend; ++x) { + const size_t xoff = static_cast(x - roi.xbegin); + Rtype* r_ptr = reinterpret_cast( + r_row + xoff * Rv.pixel_bytes); + const Atype* a_ptr = reinterpret_cast( + a_row + xoff * Av.pixel_bytes); + for (int c = roi.chbegin; c < roi.chend; ++c) { + r_ptr[c] = (Rtype)((SimdType)a_ptr[c] + (SimdType)b[c]); + } + } + } + }); + return true; +} +#endif // defined(OIIO_USE_HWY) && OIIO_USE_HWY + +template +static bool +add_impl(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, ROI roi, + int nthreads) +{ +#if defined(OIIO_USE_HWY) && OIIO_USE_HWY + if (OIIO::pvt::enable_hwy && R.localpixels() && A.localpixels() + && B.localpixels()) { + auto Rv = HwyPixels(R); + auto Av = HwyPixels(A); + auto Bv = HwyPixels(B); + const int nchannels = RoiNChannels(roi); + const bool contig = ChannelsContiguous(Rv, nchannels) + && ChannelsContiguous(Av, nchannels) + && ChannelsContiguous(Bv, nchannels); + if (contig) { + // Use native integer path for scale-invariant add when all types + // match and are integer types (much faster: 6-12x vs 3-5x with + // float conversion). + constexpr bool all_same = std::is_same_v + && std::is_same_v; + constexpr bool is_integer = std::is_integral_v; + if constexpr (all_same && is_integer) + return add_impl_hwy_native_int(R, A, B, roi, nthreads); + return add_impl_hwy(R, A, B, roi, nthreads); + } + + // Handle the common RGBA + RGB ROI strided case (preserving alpha). + if (roi.chbegin == 0 && roi.chend == 3) { + const bool contig4 = (Rv.nchannels >= 4 && Av.nchannels >= 4 + && Bv.nchannels >= 4) + && ChannelsContiguous(Rv, 4) + && ChannelsContiguous(Av, 4) + && ChannelsContiguous(Bv, 4); + if (contig4) + return add_impl_hwy(R, A, B, roi, + nthreads); + } + } +#endif + return add_impl_scalar(R, A, B, roi, nthreads); +} + +template +static bool +add_impl(ImageBuf& R, const ImageBuf& A, cspan b, ROI roi, int nthreads) +{ +#if defined(OIIO_USE_HWY) && OIIO_USE_HWY + if (OIIO::pvt::enable_hwy && R.localpixels() && A.localpixels()) + return add_impl_hwy(R, A, b, roi, nthreads); +#endif + return add_impl_scalar(R, A, b, roi, nthreads); +} + +#if defined(OIIO_USE_HWY) && OIIO_USE_HWY +// Native integer sub using SaturatedSub (scale-invariant, no float conversion) +template +static bool +sub_impl_hwy_native_int(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, + ROI roi, int nthreads) +{ + return hwy_binary_native_int_perpixel_op(R, A, B, roi, nthreads, + [](auto /*d*/, auto a, auto b) { + return hn::SaturatedSub(a, + b); + }); +} + +template +static bool +sub_impl_hwy(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, ROI roi, + int nthreads) +{ + auto op = [](auto /*d*/, auto a, auto b) { return hn::Sub(a, b); }; + + // Handle packed RGBA images with an RGB ROI (preserve alpha). + if constexpr (std::is_integral_v && std::is_same_v + && std::is_same_v) { + auto op_int = [](auto /*d*/, auto a, auto b) { + return hn::SaturatedSub(a, b); + }; + if (hwy_binary_native_int_perpixel_op_rgba_rgb_roi(R, A, B, roi, + nthreads, + op_int)) + return true; + } + if (hwy_binary_perpixel_op_rgba_rgb_roi(R, A, B, roi, + nthreads, op)) + return true; + + return hwy_binary_perpixel_op(R, A, B, roi, nthreads, + op); +} +#endif // defined(OIIO_USE_HWY) && OIIO_USE_HWY + +template +static bool +sub_impl_scalar(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, ROI roi, + int nthreads); + +template +static bool +sub_impl(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, ROI roi, + int nthreads) +{ +#if defined(OIIO_USE_HWY) && OIIO_USE_HWY + if (OIIO::pvt::enable_hwy && R.localpixels() && A.localpixels() + && B.localpixels()) { + auto Rv = HwyPixels(R); + auto Av = HwyPixels(A); + auto Bv = HwyPixels(B); + const int nchannels = RoiNChannels(roi); + const bool contig = ChannelsContiguous(Rv, nchannels) + && ChannelsContiguous(Av, nchannels) + && ChannelsContiguous(Bv, nchannels); + if (contig) { + // Use native integer path for scale-invariant sub when all types + // match and are integer types (much faster: 6-12x vs 3-5x with + // float conversion). + constexpr bool all_same = std::is_same_v + && std::is_same_v; + constexpr bool is_integer = std::is_integral_v; + if constexpr (all_same && is_integer) + return sub_impl_hwy_native_int(R, A, B, roi, nthreads); + return sub_impl_hwy(R, A, B, roi, nthreads); + } + + // Handle the common RGBA + RGB ROI strided case (preserving alpha). + if (roi.chbegin == 0 && roi.chend == 3) { + const bool contig4 = (Rv.nchannels >= 4 && Av.nchannels >= 4 + && Bv.nchannels >= 4) + && ChannelsContiguous(Rv, 4) + && ChannelsContiguous(Av, 4) + && ChannelsContiguous(Bv, 4); + if (contig4) + return sub_impl_hwy(R, A, B, roi, + nthreads); + } + } +#endif + return sub_impl_scalar(R, A, B, roi, nthreads); +} + static bool add_impl_deep(ImageBuf& R, const ImageBuf& A, cspan b, ROI roi, int nthreads) @@ -155,8 +373,8 @@ ImageBufAlgo::add(Image_or_Const A, Image_or_Const B, ROI roi, int nthreads) template static bool -sub_impl(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, ROI roi, - int nthreads) +sub_impl_scalar(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, ROI roi, + int nthreads) { ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) { ImageBuf::Iterator r(R, roi); diff --git a/src/libOpenImageIO/imagebufalgo_hwy_pvt.h b/src/libOpenImageIO/imagebufalgo_hwy_pvt.h new file mode 100644 index 0000000000..b97d4b8c30 --- /dev/null +++ b/src/libOpenImageIO/imagebufalgo_hwy_pvt.h @@ -0,0 +1,1506 @@ +// Copyright Contributors to the OpenImageIO project. +// SPDX-License-Identifier: Apache-2.0 +// https://github.com/AcademySoftwareFoundation/OpenImageIO + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +OIIO_NAMESPACE_BEGIN + +// Alias for Highway's namespace for convenience +namespace hn = hwy::HWY_NAMESPACE; + +// ----------------------------------------------------------------------- +// ImageBuf local pixel helpers (header-only) +// ----------------------------------------------------------------------- + +template struct HwyLocalPixelsView { + ByteT* base = nullptr; + size_t pixel_bytes = 0; + size_t scanline_bytes = 0; + int xbegin = 0; + int ybegin = 0; + int nchannels = 0; +}; + +inline HwyLocalPixelsView +HwyPixels(ImageBuf& img) +{ + const ImageSpec& spec = img.spec(); + return { reinterpret_cast(img.localpixels()), + static_cast(img.pixel_stride()), + static_cast(img.scanline_stride()), + img.xbegin(), + img.ybegin(), + spec.nchannels }; +} + +inline HwyLocalPixelsView +HwyPixels(const ImageBuf& img) +{ + const ImageSpec& spec = img.spec(); + return { reinterpret_cast(img.localpixels()), + static_cast(img.pixel_stride()), + static_cast(img.scanline_stride()), + img.xbegin(), + img.ybegin(), + spec.nchannels }; +} + +inline int +RoiNChannels(const ROI& roi) noexcept +{ + return roi.chend - roi.chbegin; +} + +template +inline bool +ChannelsContiguous(const HwyLocalPixelsView& v, int nchannels) noexcept +{ + return size_t(nchannels) * sizeof(T) == v.pixel_bytes; +} + +template +inline ByteT* +PixelBase(const HwyLocalPixelsView& v, int x, int y) noexcept +{ + return v.base + size_t(y - v.ybegin) * v.scanline_bytes + + size_t(x - v.xbegin) * v.pixel_bytes; +} + +template +inline std::conditional_t, const T*, T*> +ChannelPtr(const HwyLocalPixelsView& v, int x, int y, int ch) noexcept +{ + using RetT = std::conditional_t, const T, T>; + return reinterpret_cast(PixelBase(v, x, y) + size_t(ch) * sizeof(T)); +} + +template +inline std::conditional_t, const T*, T*> +RoiRowPtr(const HwyLocalPixelsView& v, int y, const ROI& roi) noexcept +{ + return ChannelPtr(v, roi.xbegin, y, roi.chbegin); +} + +// ----------------------------------------------------------------------- +// Type Traits +// ----------------------------------------------------------------------- + +/// Determine the appropriate SIMD math type for a given result type. +/// Promotes smaller types to float, keeps double as double. +/// Note: uint32_t uses float (not double) for image processing performance. +/// In OIIO, uint32 images are normalized to 0-1 range like uint8/uint16, +/// so float precision (24-bit mantissa) is sufficient and much faster than double. +template struct SimdMathType { + using type = float; +}; +template<> struct SimdMathType { + using type = double; +}; + +// Half is stored as OIIO::half in memory, but HWY's interleaved load/store uses +// hwy::float16_t lanes. +template struct HwyLaneType { + using type = T; +}; +template<> struct HwyLaneType { + using type = hwy::float16_t; +}; +template using HwyLaneTypeT = typename HwyLaneType::type; + +// Forward declarations (needed for templates that use these helpers before +// their definitions later in this header). +template +inline std::tuple, hn::Vec, hn::Vec, hn::Vec> +LoadInterleaved4Promote(D d, const SrcT* ptr); + +template +inline std::tuple, hn::Vec, hn::Vec, hn::Vec> +LoadInterleaved4PromoteN(D d, const SrcT* ptr, size_t count); + +template +inline void +StoreInterleaved4RgbAlphaPassthrough(D d, DstT* ptr, VecMathT r, VecMathT g, + VecMathT b, VecAlphaLaneT a_passthrough); + +// ----------------------------------------------------------------------- +// Load and Promote +// ----------------------------------------------------------------------- + +/// Load and promote source data to target SIMD type. +/// Handles type conversions from various source formats (uint8_t, int8_t, uint16_t, +/// int16_t, uint32_t, int32_t, uint64_t, int64_t, half, float, double) to the +/// target SIMD computation type. +/// @param d Highway descriptor tag defining the target SIMD type +/// @param ptr Pointer to source data (may be unaligned) +/// @return SIMD vector with promoted values +template +inline auto +LoadPromote(D d, const SrcT* ptr) +{ + using MathT = typename D::T; + + if constexpr (std::is_same_v) { + return hn::Load(d, ptr); + } else if constexpr (std::is_same_v) { + using T16 = hwy::float16_t; + auto d16 = hn::Rebind(); + auto v16 = hn::Load(d16, reinterpret_cast(ptr)); + return hn::PromoteTo(d, v16); + } else if constexpr (std::is_same_v) { + auto d_u8 = hn::Rebind(); + auto v_u8 = hn::Load(d_u8, ptr); + auto v_promoted = hn::ConvertTo( + d, hn::PromoteTo(hn::Rebind(), + hn::PromoteTo(hn::Rebind(), v_u8))); + // Normalize to 0-1 range for image operations + return hn::Mul(v_promoted, hn::Set(d, (MathT)(1.0 / 255.0))); + } else if constexpr (std::is_same_v) { + auto d_i8 = hn::Rebind(); + auto v_i8 = hn::Load(d_i8, ptr); + auto v_promoted = hn::ConvertTo( + d, hn::PromoteTo(hn::Rebind(), + hn::PromoteTo(hn::Rebind(), v_i8))); + // Normalize: map [-128, 127] to approximately [-1.0, 1.0] + // Clamp INT_MIN so we never produce values < -1.0. + auto v_norm = hn::Mul(v_promoted, hn::Set(d, (MathT)(1.0 / 127.0))); + return hn::Max(v_norm, hn::Set(d, (MathT)-1.0)); + } else if constexpr (std::is_same_v) { + auto d_u16 = hn::Rebind(); + auto v_u16 = hn::Load(d_u16, ptr); + auto v_promoted + = hn::ConvertTo(d, hn::PromoteTo(hn::Rebind(), v_u16)); + // Normalize to 0-1 range for image operations + return hn::Mul(v_promoted, hn::Set(d, (MathT)(1.0 / 65535.0))); + } else if constexpr (std::is_same_v) { + auto d_i16 = hn::Rebind(); + auto v_i16 = hn::Load(d_i16, ptr); + auto v_promoted + = hn::ConvertTo(d, hn::PromoteTo(hn::Rebind(), v_i16)); + // Normalize: map [-32768, 32767] to approximately [-1.0, 1.0] + // Clamp INT_MIN so we never produce values < -1.0. + auto v_norm = hn::Mul(v_promoted, hn::Set(d, (MathT)(1.0 / 32767.0))); + return hn::Max(v_norm, hn::Set(d, (MathT)-1.0)); + } else if constexpr (std::is_same_v) { + // uint32 to float: Load, convert, and normalize to 0-1 range + auto d_u32 = hn::Rebind(); + auto v_u32 = hn::Load(d_u32, ptr); + auto v_promoted = hn::ConvertTo(d, v_u32); + // Normalize to 0-1 range for image operations + return hn::Mul(v_promoted, hn::Set(d, (MathT)(1.0 / 4294967295.0))); + } else if constexpr (std::is_same_v) { + // int32 to float: Load, convert, and normalize to approximately [-1.0, 1.0] + auto d_i32 = hn::Rebind(); + auto v_i32 = hn::Load(d_i32, ptr); + auto v_promoted = hn::ConvertTo(d, v_i32); + // Normalize: map [-2147483648, 2147483647] to approximately [-1.0, 1.0] + // Clamp INT_MIN so we never produce values < -1.0. + auto v_norm = hn::Mul(v_promoted, + hn::Set(d, (MathT)(1.0 / 2147483647.0))); + return hn::Max(v_norm, hn::Set(d, (MathT)-1.0)); + } else if constexpr (std::is_same_v) { + // uint64 to float: Load and demote to uint32, then convert + // Note: Precision loss expected for large values (>24 bits) + auto d_u64 = hn::Rebind(); + auto v_u64 = hn::Load(d_u64, ptr); + auto d_u32 = hn::Rebind(); + auto v_u32 = hn::DemoteTo(d_u32, v_u64); + return hn::ConvertTo(d, v_u32); + } else if constexpr (std::is_same_v) { + // int64 to float: Load and demote to int32, then convert + auto d_i64 = hn::Rebind(); + auto v_i64 = hn::Load(d_i64, ptr); + auto d_i32 = hn::Rebind(); + auto v_i32 = hn::DemoteTo(d_i32, v_i64); + return hn::ConvertTo(d, v_i32); + } else { + return hn::Zero(d); + } +} + +/// Promote an already-loaded SIMD vector to the math type for `d`. +/// This mirrors LoadPromote()'s conversion and normalization semantics but +/// avoids re-loading from memory (useful for interleaved loads). +template +inline auto +PromoteVec(D d, VecT v) +{ + using MathT = typename D::T; + if constexpr (std::is_same_v) { + return v; + } else if constexpr (std::is_same_v) { + return hn::PromoteTo(d, v); + } else if constexpr (std::is_same_v) { + auto v_promoted = hn::ConvertTo( + d, hn::PromoteTo(hn::Rebind(), + hn::PromoteTo(hn::Rebind(), v))); + return hn::Mul(v_promoted, hn::Set(d, (MathT)(1.0 / 255.0))); + } else if constexpr (std::is_same_v) { + auto v_promoted = hn::ConvertTo( + d, hn::PromoteTo(hn::Rebind(), + hn::PromoteTo(hn::Rebind(), v))); + auto v_norm = hn::Mul(v_promoted, hn::Set(d, (MathT)(1.0 / 127.0))); + return hn::Max(v_norm, hn::Set(d, (MathT)-1.0)); + } else if constexpr (std::is_same_v) { + auto v_promoted + = hn::ConvertTo(d, hn::PromoteTo(hn::Rebind(), v)); + return hn::Mul(v_promoted, hn::Set(d, (MathT)(1.0 / 65535.0))); + } else if constexpr (std::is_same_v) { + auto v_promoted + = hn::ConvertTo(d, hn::PromoteTo(hn::Rebind(), v)); + auto v_norm = hn::Mul(v_promoted, hn::Set(d, (MathT)(1.0 / 32767.0))); + return hn::Max(v_norm, hn::Set(d, (MathT)-1.0)); + } else if constexpr (std::is_same_v) { + auto v_promoted = hn::ConvertTo(d, v); + return hn::Mul(v_promoted, hn::Set(d, (MathT)(1.0 / 4294967295.0))); + } else if constexpr (std::is_same_v) { + auto v_promoted = hn::ConvertTo(d, v); + auto v_norm = hn::Mul(v_promoted, + hn::Set(d, (MathT)(1.0 / 2147483647.0))); + return hn::Max(v_norm, hn::Set(d, (MathT)-1.0)); + } else if constexpr (std::is_same_v) { + auto d_u32 = hn::Rebind(); + auto v_u32 = hn::DemoteTo(d_u32, v); + return hn::ConvertTo(d, v_u32); + } else if constexpr (std::is_same_v) { + auto d_i32 = hn::Rebind(); + auto v_i32 = hn::DemoteTo(d_i32, v); + return hn::ConvertTo(d, v_i32); + } else { + return hn::Zero(d); + } +} + +/// Load and promote partial source data to target SIMD type. +/// Same as LoadPromote but handles partial vectors (< full lane count). +/// @param d Highway descriptor tag defining the target SIMD type +/// @param ptr Pointer to source data (may be unaligned) +/// @param count Number of elements to load (must be <= lane count) +/// @return SIMD vector with promoted values (undefined in unused lanes) +template +inline auto +LoadPromoteN(D d, const SrcT* ptr, size_t count) +{ + using MathT = typename D::T; + + if constexpr (std::is_same_v) { + return hn::LoadN(d, ptr, count); + } else if constexpr (std::is_same_v) { + using T16 = hwy::float16_t; + auto d16 = hn::Rebind(); + auto v16 = hn::LoadN(d16, reinterpret_cast(ptr), count); + return hn::PromoteTo(d, v16); + } else if constexpr (std::is_same_v) { + auto d_u8 = hn::Rebind(); + auto v_u8 = hn::LoadN(d_u8, ptr, count); + auto v_promoted = hn::ConvertTo( + d, hn::PromoteTo(hn::Rebind(), + hn::PromoteTo(hn::Rebind(), v_u8))); + // Normalize to 0-1 range for image operations + return hn::Mul(v_promoted, hn::Set(d, (MathT)(1.0 / 255.0))); + } else if constexpr (std::is_same_v) { + auto d_i8 = hn::Rebind(); + auto v_i8 = hn::LoadN(d_i8, ptr, count); + auto v_promoted = hn::ConvertTo( + d, hn::PromoteTo(hn::Rebind(), + hn::PromoteTo(hn::Rebind(), v_i8))); + // Normalize: map [-128, 127] to approximately [-1.0, 1.0] + // Clamp INT_MIN so we never produce values < -1.0. + auto v_norm = hn::Mul(v_promoted, hn::Set(d, (MathT)(1.0 / 127.0))); + return hn::Max(v_norm, hn::Set(d, (MathT)-1.0)); + } else if constexpr (std::is_same_v) { + auto d_u16 = hn::Rebind(); + auto v_u16 = hn::LoadN(d_u16, ptr, count); + auto v_promoted + = hn::ConvertTo(d, hn::PromoteTo(hn::Rebind(), v_u16)); + // Normalize to 0-1 range for image operations + return hn::Mul(v_promoted, hn::Set(d, (MathT)(1.0 / 65535.0))); + } else if constexpr (std::is_same_v) { + auto d_i16 = hn::Rebind(); + auto v_i16 = hn::LoadN(d_i16, ptr, count); + auto v_promoted + = hn::ConvertTo(d, hn::PromoteTo(hn::Rebind(), v_i16)); + // Normalize: map [-32768, 32767] to approximately [-1.0, 1.0] + // Clamp INT_MIN so we never produce values < -1.0. + auto v_norm = hn::Mul(v_promoted, hn::Set(d, (MathT)(1.0 / 32767.0))); + return hn::Max(v_norm, hn::Set(d, (MathT)-1.0)); + } else if constexpr (std::is_same_v) { + // uint32 to float: Load, convert, and normalize to 0-1 range + auto d_u32 = hn::Rebind(); + auto v_u32 = hn::LoadN(d_u32, ptr, count); + auto v_promoted = hn::ConvertTo(d, v_u32); + // Normalize to 0-1 range for image operations + return hn::Mul(v_promoted, hn::Set(d, (MathT)(1.0 / 4294967295.0))); + } else if constexpr (std::is_same_v) { + // int32 to float: Load, convert, and normalize to approximately [-1.0, 1.0] + auto d_i32 = hn::Rebind(); + auto v_i32 = hn::LoadN(d_i32, ptr, count); + auto v_promoted = hn::ConvertTo(d, v_i32); + // Normalize: map [-2147483648, 2147483647] to approximately [-1.0, 1.0] + // Clamp INT_MIN so we never produce values < -1.0. + auto v_norm = hn::Mul(v_promoted, + hn::Set(d, (MathT)(1.0 / 2147483647.0))); + return hn::Max(v_norm, hn::Set(d, (MathT)-1.0)); + } else if constexpr (std::is_same_v) { + // uint64 to float: Load and demote to uint32, then convert + auto d_u64 = hn::Rebind(); + auto v_u64 = hn::LoadN(d_u64, ptr, count); + auto d_u32 = hn::Rebind(); + auto v_u32 = hn::DemoteTo(d_u32, v_u64); + return hn::ConvertTo(d, v_u32); + } else if constexpr (std::is_same_v) { + // int64 to float: Load and demote to int32, then convert + auto d_i64 = hn::Rebind(); + auto v_i64 = hn::LoadN(d_i64, ptr, count); + auto d_i32 = hn::Rebind(); + auto v_i32 = hn::DemoteTo(d_i32, v_i64); + return hn::ConvertTo(d, v_i32); + } else { + return hn::Zero(d); + } +} + +// ----------------------------------------------------------------------- +// Demote and Store +// ----------------------------------------------------------------------- + +/// Demote SIMD values and store to destination type. +/// Handles type conversions from SIMD computation type (float/double) back to +/// various destination formats with proper rounding and clamping for integer types. +/// @param d Highway descriptor tag for the source SIMD type +/// @param ptr Pointer to destination data (may be unaligned) +/// @param v SIMD vector to demote and store +template +inline void +DemoteStore(D d, DstT* ptr, VecT v) +{ + using MathT = typename D::T; + using VecD = hn::Vec; + + if constexpr (std::is_same_v) { + hn::Store(v, d, ptr); + } else if constexpr (std::is_same_v) { + auto d16 = hn::Rebind(); + auto v16 = hn::DemoteTo(d16, v); + hn::Store(v16, d16, reinterpret_cast(ptr)); + } else if constexpr (std::is_same_v) { + VecD v_val = (VecD)v; + // Denormalize from 0-1 range to 0-255 range + VecD v_denorm = hn::Mul(v_val, hn::Set(d, (MathT)255.0)); + VecD v_rounded = hn::Add(v_denorm, hn::Set(d, (MathT)0.5)); + VecD v_zero = hn::Zero(d); + VecD v_max = hn::Set(d, (MathT)255.0); + VecD v_clamped = hn::Max(v_rounded, v_zero); + v_clamped = hn::Min(v_clamped, v_max); + + auto d32 = hn::Rebind(); + auto vi32 = hn::ConvertTo(d32, v_clamped); + auto d_i16 = hn::Rebind(); + auto v_i16 = hn::DemoteTo(d_i16, vi32); + auto d_u8 = hn::Rebind(); + auto v_u8 = hn::DemoteTo(d_u8, v_i16); + hn::Store(v_u8, d_u8, ptr); + } else if constexpr (std::is_same_v) { + VecD v_val = (VecD)v; + // Denormalize from approximately [-1.0, 1.0] range to -128-127 range + VecD v_denorm = hn::Mul(v_val, hn::Set(d, (MathT)127.0)); + // Symmetric round-to-nearest for signed values (assumes ConvertTo truncates). + auto is_neg = hn::Lt(v_denorm, hn::Zero(d)); + auto v_bias = hn::IfThenElse(is_neg, hn::Set(d, (MathT)-0.5), + hn::Set(d, (MathT)0.5)); + VecD v_rounded = hn::Add(v_denorm, v_bias); + VecD v_min = hn::Set(d, (MathT)-128.0); + VecD v_max = hn::Set(d, (MathT)127.0); + VecD v_clamped = hn::Max(v_rounded, v_min); + v_clamped = hn::Min(v_clamped, v_max); + + auto d32 = hn::Rebind(); + auto vi32 = hn::ConvertTo(d32, v_clamped); + auto d_i16 = hn::Rebind(); + auto v_i16 = hn::DemoteTo(d_i16, vi32); + auto d_i8 = hn::Rebind(); + auto v_i8 = hn::DemoteTo(d_i8, v_i16); + hn::Store(v_i8, d_i8, ptr); + } else if constexpr (std::is_same_v) { + VecD v_val = (VecD)v; + // Denormalize from 0-1 range to 0-65535 range + VecD v_denorm = hn::Mul(v_val, hn::Set(d, (MathT)65535.0)); + VecD v_rounded = hn::Add(v_denorm, hn::Set(d, (MathT)0.5)); + VecD v_zero = hn::Zero(d); + VecD v_max = hn::Set(d, (MathT)65535.0); + VecD v_clamped = hn::Max(v_rounded, v_zero); + v_clamped = hn::Min(v_clamped, v_max); + + auto d32 = hn::Rebind(); + auto vi32 = hn::ConvertTo(d32, v_clamped); + auto d_u16 = hn::Rebind(); + auto v_u16 = hn::DemoteTo(d_u16, vi32); + hn::Store(v_u16, d_u16, ptr); + } else if constexpr (std::is_same_v) { + VecD v_val = (VecD)v; + // Denormalize from approximately [-1.0, 1.0] range to -32768-32767 range + VecD v_denorm = hn::Mul(v_val, hn::Set(d, (MathT)32767.0)); + // Symmetric round-to-nearest for signed values (assumes ConvertTo truncates). + auto is_neg = hn::Lt(v_denorm, hn::Zero(d)); + auto v_bias = hn::IfThenElse(is_neg, hn::Set(d, (MathT)-0.5), + hn::Set(d, (MathT)0.5)); + VecD v_rounded = hn::Add(v_denorm, v_bias); + VecD v_min = hn::Set(d, (MathT)-32768.0); + VecD v_max = hn::Set(d, (MathT)32767.0); + VecD v_clamped = hn::Max(v_rounded, v_min); + v_clamped = hn::Min(v_clamped, v_max); + + auto d32 = hn::Rebind(); + auto vi32 = hn::ConvertTo(d32, v_clamped); + auto d_i16 = hn::Rebind(); + auto v_i16 = hn::DemoteTo(d_i16, vi32); + hn::Store(v_i16, d_i16, ptr); + } else if constexpr (std::is_same_v) { + // float -> uint32: Denormalize from 0-1 to 0-4294967295, round and convert + VecD v_val = (VecD)v; + // Denormalize from 0-1 range to 0-4294967295 range + VecD v_denorm = hn::Mul(v_val, hn::Set(d, (MathT)4294967295.0)); + VecD v_rounded = hn::Add(v_denorm, hn::Set(d, (MathT)0.5)); + VecD v_zero = hn::Zero(d); + VecD v_max = hn::Set(d, (MathT)4294967295.0); + VecD v_clamped = hn::Max(v_rounded, v_zero); + v_clamped = hn::Min(v_clamped, v_max); + + auto d_u32 = hn::Rebind(); + auto v_u32 = hn::ConvertTo(d_u32, v_clamped); + hn::Store(v_u32, d_u32, ptr); + } else if constexpr (std::is_same_v) { + // float -> int32: Denormalize from approximately [-1.0, 1.0] to int32 range + VecD v_val = (VecD)v; + // Denormalize from approximately [-1.0, 1.0] range to -2147483648-2147483647 range + VecD v_denorm = hn::Mul(v_val, hn::Set(d, (MathT)2147483647.0)); + // Symmetric round-to-nearest for signed values (assumes ConvertTo truncates). + auto is_neg = hn::Lt(v_denorm, hn::Zero(d)); + auto v_bias = hn::IfThenElse(is_neg, hn::Set(d, (MathT)-0.5), + hn::Set(d, (MathT)0.5)); + VecD v_rounded = hn::Add(v_denorm, v_bias); + VecD v_min = hn::Set(d, (MathT)-2147483648.0); + VecD v_max = hn::Set(d, (MathT)2147483647.0); + VecD v_clamped = hn::Max(v_rounded, v_min); + v_clamped = hn::Min(v_clamped, v_max); + + auto d_i32 = hn::Rebind(); + auto v_i32 = hn::ConvertTo(d_i32, v_clamped); + hn::Store(v_i32, d_i32, ptr); + } else if constexpr (std::is_same_v) { + // float -> uint64: Promote via uint32 + // Note: Precision loss expected (float has only 24-bit mantissa) + VecD v_val = (VecD)v; + VecD v_rounded = hn::Add(v_val, hn::Set(d, (MathT)0.5)); + VecD v_zero = hn::Zero(d); + VecD v_clamped = hn::Max(v_rounded, v_zero); + + auto d_u32 = hn::Rebind(); + auto v_u32 = hn::ConvertTo(d_u32, v_clamped); + auto d_u64 = hn::Rebind(); + auto v_u64 = hn::PromoteTo(d_u64, v_u32); + hn::Store(v_u64, d_u64, ptr); + } else if constexpr (std::is_same_v) { + // float -> int64: Promote via int32 + VecD v_val = (VecD)v; + VecD v_rounded = hn::Add(v_val, hn::Set(d, (MathT)0.5)); + + auto d_i32 = hn::Rebind(); + auto v_i32 = hn::ConvertTo(d_i32, v_rounded); + auto d_i64 = hn::Rebind(); + auto v_i64 = hn::PromoteTo(d_i64, v_i32); + hn::Store(v_i64, d_i64, ptr); + } +} + +/// Demote a SIMD math vector to the destination lane type (no store). +/// This mirrors DemoteStore()'s rounding/clamping semantics. +template +inline auto +DemoteVec(D d, VecT v) +{ + using MathT = typename D::T; + using VecD = hn::Vec; + if constexpr (std::is_same_v) { + return v; + } else if constexpr (std::is_same_v) { + auto d16 = hn::Rebind(); + return hn::DemoteTo(d16, v); + } else if constexpr (std::is_same_v) { + VecD v_denorm = hn::Mul((VecD)v, hn::Set(d, (MathT)255.0)); + VecD v_rounded = hn::Add(v_denorm, hn::Set(d, (MathT)0.5)); + VecD v_clamped = hn::Max(v_rounded, hn::Zero(d)); + v_clamped = hn::Min(v_clamped, hn::Set(d, (MathT)255.0)); + + auto d32 = hn::Rebind(); + auto vi32 = hn::ConvertTo(d32, v_clamped); + auto d_i16 = hn::Rebind(); + auto v_i16 = hn::DemoteTo(d_i16, vi32); + auto d_u8 = hn::Rebind(); + return hn::DemoteTo(d_u8, v_i16); + } else if constexpr (std::is_same_v) { + VecD v_denorm = hn::Mul((VecD)v, hn::Set(d, (MathT)127.0)); + auto is_neg = hn::Lt(v_denorm, hn::Zero(d)); + auto v_bias = hn::IfThenElse(is_neg, hn::Set(d, (MathT)-0.5), + hn::Set(d, (MathT)0.5)); + VecD v_rounded = hn::Add(v_denorm, v_bias); + VecD v_clamped = hn::Max(v_rounded, hn::Set(d, (MathT)-128.0)); + v_clamped = hn::Min(v_clamped, hn::Set(d, (MathT)127.0)); + + auto d32 = hn::Rebind(); + auto vi32 = hn::ConvertTo(d32, v_clamped); + auto d_i16 = hn::Rebind(); + auto v_i16 = hn::DemoteTo(d_i16, vi32); + auto d_i8 = hn::Rebind(); + return hn::DemoteTo(d_i8, v_i16); + } else if constexpr (std::is_same_v) { + VecD v_denorm = hn::Mul((VecD)v, hn::Set(d, (MathT)65535.0)); + VecD v_rounded = hn::Add(v_denorm, hn::Set(d, (MathT)0.5)); + VecD v_clamped = hn::Max(v_rounded, hn::Zero(d)); + v_clamped = hn::Min(v_clamped, hn::Set(d, (MathT)65535.0)); + + auto d32 = hn::Rebind(); + auto vi32 = hn::ConvertTo(d32, v_clamped); + auto d_u16 = hn::Rebind(); + return hn::DemoteTo(d_u16, vi32); + } else if constexpr (std::is_same_v) { + VecD v_denorm = hn::Mul((VecD)v, hn::Set(d, (MathT)32767.0)); + auto is_neg = hn::Lt(v_denorm, hn::Zero(d)); + auto v_bias = hn::IfThenElse(is_neg, hn::Set(d, (MathT)-0.5), + hn::Set(d, (MathT)0.5)); + VecD v_rounded = hn::Add(v_denorm, v_bias); + VecD v_clamped = hn::Max(v_rounded, hn::Set(d, (MathT)-32768.0)); + v_clamped = hn::Min(v_clamped, hn::Set(d, (MathT)32767.0)); + + auto d32 = hn::Rebind(); + auto vi32 = hn::ConvertTo(d32, v_clamped); + auto d_i16 = hn::Rebind(); + return hn::DemoteTo(d_i16, vi32); + } else if constexpr (std::is_same_v) { + VecD v_denorm = hn::Mul((VecD)v, hn::Set(d, (MathT)4294967295.0)); + VecD v_rounded = hn::Add(v_denorm, hn::Set(d, (MathT)0.5)); + VecD v_clamped = hn::Max(v_rounded, hn::Zero(d)); + auto d_u32 = hn::Rebind(); + return hn::ConvertTo(d_u32, v_clamped); + } else if constexpr (std::is_same_v) { + VecD v_denorm = hn::Mul((VecD)v, hn::Set(d, (MathT)2147483647.0)); + auto is_neg = hn::Lt(v_denorm, hn::Zero(d)); + auto v_bias = hn::IfThenElse(is_neg, hn::Set(d, (MathT)-0.5), + hn::Set(d, (MathT)0.5)); + VecD v_rounded = hn::Add(v_denorm, v_bias); + VecD v_clamped = hn::Max(v_rounded, hn::Set(d, (MathT)-2147483648.0)); + v_clamped = hn::Min(v_clamped, hn::Set(d, (MathT)2147483647.0)); + auto d_i32 = hn::Rebind(); + return hn::ConvertTo(d_i32, v_clamped); + } else { + auto d_dst = hn::Rebind(); + return hn::Zero(d_dst); + } +} + +/// Demote and store partial SIMD values to destination type. +/// Same as DemoteStore but handles partial vectors (< full lane count). +/// @param d Highway descriptor tag for the source SIMD type +/// @param ptr Pointer to destination data (may be unaligned) +/// @param v SIMD vector to demote and store +/// @param count Number of elements to store (must be <= lane count) +template +inline void +DemoteStoreN(D d, DstT* ptr, VecT v, size_t count) +{ + using MathT = typename D::T; + using VecD = hn::Vec; + + if constexpr (std::is_same_v) { + hn::StoreN(v, d, ptr, count); + } else if constexpr (std::is_same_v) { + auto d16 = hn::Rebind(); + auto v16 = hn::DemoteTo(d16, v); + hn::StoreN(v16, d16, reinterpret_cast(ptr), count); + } else if constexpr (std::is_same_v) { + VecD v_val = (VecD)v; + // Denormalize from 0-1 range to 0-255 range + VecD v_denorm = hn::Mul(v_val, hn::Set(d, (MathT)255.0)); + VecD v_rounded = hn::Add(v_denorm, hn::Set(d, (MathT)0.5)); + VecD v_zero = hn::Zero(d); + VecD v_max = hn::Set(d, (MathT)255.0); + VecD v_clamped = hn::Max(v_rounded, v_zero); + v_clamped = hn::Min(v_clamped, v_max); + + auto d32 = hn::Rebind(); + auto vi32 = hn::ConvertTo(d32, v_clamped); + auto d_i16 = hn::Rebind(); + auto v_i16 = hn::DemoteTo(d_i16, vi32); + auto d_u8 = hn::Rebind(); + auto v_u8 = hn::DemoteTo(d_u8, v_i16); + hn::StoreN(v_u8, d_u8, ptr, count); + } else if constexpr (std::is_same_v) { + VecD v_val = (VecD)v; + // Denormalize from approximately [-1.0, 1.0] range to [-128, 127] range + VecD v_denorm = hn::Mul(v_val, hn::Set(d, (MathT)127.0)); + // Symmetric round-to-nearest for signed values (assumes ConvertTo truncates). + auto is_neg = hn::Lt(v_denorm, hn::Zero(d)); + auto v_bias = hn::IfThenElse(is_neg, hn::Set(d, (MathT)-0.5), + hn::Set(d, (MathT)0.5)); + VecD v_rounded = hn::Add(v_denorm, v_bias); + VecD v_min = hn::Set(d, (MathT)-128.0); + VecD v_max = hn::Set(d, (MathT)127.0); + VecD v_clamped = hn::Max(v_rounded, v_min); + v_clamped = hn::Min(v_clamped, v_max); + + auto d32 = hn::Rebind(); + auto vi32 = hn::ConvertTo(d32, v_clamped); + auto d_i16 = hn::Rebind(); + auto v_i16 = hn::DemoteTo(d_i16, vi32); + auto d_i8 = hn::Rebind(); + auto v_i8 = hn::DemoteTo(d_i8, v_i16); + hn::StoreN(v_i8, d_i8, ptr, count); + } else if constexpr (std::is_same_v) { + VecD v_val = (VecD)v; + // Denormalize from 0-1 range to 0-65535 range + VecD v_denorm = hn::Mul(v_val, hn::Set(d, (MathT)65535.0)); + VecD v_rounded = hn::Add(v_denorm, hn::Set(d, (MathT)0.5)); + VecD v_zero = hn::Zero(d); + VecD v_max = hn::Set(d, (MathT)65535.0); + VecD v_clamped = hn::Max(v_rounded, v_zero); + v_clamped = hn::Min(v_clamped, v_max); + + auto d32 = hn::Rebind(); + auto vi32 = hn::ConvertTo(d32, v_clamped); + auto d_u16 = hn::Rebind(); + auto v_u16 = hn::DemoteTo(d_u16, vi32); + hn::StoreN(v_u16, d_u16, ptr, count); + } else if constexpr (std::is_same_v) { + VecD v_val = (VecD)v; + // Denormalize from approximately [-1.0, 1.0] range to [-32768, 32767] range + VecD v_denorm = hn::Mul(v_val, hn::Set(d, (MathT)32767.0)); + // Symmetric round-to-nearest for signed values (assumes ConvertTo truncates). + auto is_neg = hn::Lt(v_denorm, hn::Zero(d)); + auto v_bias = hn::IfThenElse(is_neg, hn::Set(d, (MathT)-0.5), + hn::Set(d, (MathT)0.5)); + VecD v_rounded = hn::Add(v_denorm, v_bias); + VecD v_min = hn::Set(d, (MathT)-32768.0); + VecD v_max = hn::Set(d, (MathT)32767.0); + VecD v_clamped = hn::Max(v_rounded, v_min); + v_clamped = hn::Min(v_clamped, v_max); + + auto d32 = hn::Rebind(); + auto vi32 = hn::ConvertTo(d32, v_clamped); + auto d_i16 = hn::Rebind(); + auto v_i16 = hn::DemoteTo(d_i16, vi32); + hn::StoreN(v_i16, d_i16, ptr, count); + } else if constexpr (std::is_same_v) { + // float -> uint32: Denormalize from 0-1 to 0-4294967295, round and convert + VecD v_val = (VecD)v; + // Denormalize from 0-1 range to 0-4294967295 range + VecD v_denorm = hn::Mul(v_val, hn::Set(d, (MathT)4294967295.0)); + VecD v_rounded = hn::Add(v_denorm, hn::Set(d, (MathT)0.5)); + VecD v_zero = hn::Zero(d); + VecD v_max = hn::Set(d, (MathT)4294967295.0); + VecD v_clamped = hn::Max(v_rounded, v_zero); + v_clamped = hn::Min(v_clamped, v_max); + + auto d_u32 = hn::Rebind(); + auto v_u32 = hn::ConvertTo(d_u32, v_clamped); + hn::StoreN(v_u32, d_u32, ptr, count); + } else if constexpr (std::is_same_v) { + // float -> int32: Denormalize from approximately [-1.0, 1.0] range to [-2147483648, 2147483647] range + VecD v_val = (VecD)v; + VecD v_denorm = hn::Mul(v_val, hn::Set(d, (MathT)2147483647.0)); + // Symmetric round-to-nearest for signed values (assumes ConvertTo truncates). + auto is_neg = hn::Lt(v_denorm, hn::Zero(d)); + auto v_bias = hn::IfThenElse(is_neg, hn::Set(d, (MathT)-0.5), + hn::Set(d, (MathT)0.5)); + VecD v_rounded = hn::Add(v_denorm, v_bias); + VecD v_min = hn::Set(d, (MathT)-2147483648.0); + VecD v_max = hn::Set(d, (MathT)2147483647.0); + VecD v_clamped = hn::Max(v_rounded, v_min); + v_clamped = hn::Min(v_clamped, v_max); + + auto d_i32 = hn::Rebind(); + auto v_i32 = hn::ConvertTo(d_i32, v_clamped); + hn::StoreN(v_i32, d_i32, ptr, count); + } else if constexpr (std::is_same_v) { + // float -> uint64: Promote via uint32 + VecD v_val = (VecD)v; + VecD v_rounded = hn::Add(v_val, hn::Set(d, (MathT)0.5)); + VecD v_zero = hn::Zero(d); + VecD v_clamped = hn::Max(v_rounded, v_zero); + + auto d_u32 = hn::Rebind(); + auto v_u32 = hn::ConvertTo(d_u32, v_clamped); + auto d_u64 = hn::Rebind(); + auto v_u64 = hn::PromoteTo(d_u64, v_u32); + hn::StoreN(v_u64, d_u64, ptr, count); + } else if constexpr (std::is_same_v) { + // float -> int64: Promote via int32 + VecD v_val = (VecD)v; + VecD v_rounded = hn::Add(v_val, hn::Set(d, (MathT)0.5)); + + auto d_i32 = hn::Rebind(); + auto v_i32 = hn::ConvertTo(d_i32, v_rounded); + auto d_i64 = hn::Rebind(); + auto v_i64 = hn::PromoteTo(d_i64, v_i32); + hn::StoreN(v_i64, d_i64, ptr, count); + } +} + +// ----------------------------------------------------------------------- +// Native Integer Kernel Runners (No Type Conversion) +// ----------------------------------------------------------------------- + +/// Execute a unary SIMD operation on native integer arrays (no type promotion). +/// For scale-invariant operations like abs, where int_op(a) == denorm(float_op(norm(a))). +/// Much faster than promotion path - operates directly on integer SIMD vectors. +/// @param r Destination array (same type as source) +/// @param a Source array +/// @param n Number of elements to process +/// @param op Lambda/functor taking (descriptor, vector) and returning result vector +/// Example: [](auto d, auto va) { return hn::Abs(va); } +template +inline void +RunHwyUnaryNativeInt(T* r, const T* a, size_t n, OpFunc op) +{ + const hn::ScalableTag d; + size_t x = 0; + size_t lanes = hn::Lanes(d); + for (; x + lanes <= n; x += lanes) { + auto va = hn::Load(d, a + x); + auto res = op(d, va); + hn::Store(res, d, r + x); + } + size_t remaining = n - x; + if (remaining > 0) { + auto va = hn::LoadN(d, a + x, remaining); + auto res = op(d, va); + hn::StoreN(res, d, r + x, remaining); + } +} + +/// Execute a binary SIMD operation on native integer arrays (no type promotion). +/// For scale-invariant operations like saturated add, min, max, where: +/// int_op(a, b) == denorm(float_op(norm(a), norm(b))). +/// Much faster than promotion path - no conversion overhead. +/// @param r Destination array (same type as sources) +/// @param a First source array +/// @param b Second source array +/// @param n Number of elements to process +/// @param op Lambda/functor taking (descriptor, vector_a, vector_b) and returning result +/// Example: [](auto d, auto va, auto vb) { return hn::SaturatedAdd(va, vb); } +template +inline void +RunHwyBinaryNativeInt(T* r, const T* a, const T* b, size_t n, OpFunc op) +{ + const hn::ScalableTag d; + size_t x = 0; + size_t lanes = hn::Lanes(d); + for (; x + lanes <= n; x += lanes) { + auto va = hn::Load(d, a + x); + auto vb = hn::Load(d, b + x); + auto res = op(d, va, vb); + hn::Store(res, d, r + x); + } + size_t remaining = n - x; + if (remaining > 0) { + auto va = hn::LoadN(d, a + x, remaining); + auto vb = hn::LoadN(d, b + x, remaining); + auto res = op(d, va, vb); + hn::StoreN(res, d, r + x, remaining); + } +} + +// ----------------------------------------------------------------------- +// Generic Kernel Runners (With Type Conversion) +// ----------------------------------------------------------------------- + +/// Execute a unary SIMD operation on an array. +/// Processes array elements in SIMD batches, handling type promotion/demotion +/// and partial vectors at the end. +/// @param r Destination array +/// @param a Source array +/// @param n Number of elements to process +/// @param op Lambda/functor taking (descriptor, vector) and returning result vector +/// Example: [](auto d, auto va) { return hn::Sqrt(va); } +template +inline void +RunHwyUnaryCmd(Rtype* r, const Atype* a, size_t n, OpFunc op) +{ + using MathT = typename SimdMathType::type; + const hn::ScalableTag d; + size_t x = 0; + size_t lanes = hn::Lanes(d); + for (; x + lanes <= n; x += lanes) { + auto va = LoadPromote(d, a + x); + auto res = op(d, va); + DemoteStore(d, r + x, res); + } + size_t remaining = n - x; + if (remaining > 0) { + auto va = LoadPromoteN(d, a + x, remaining); + auto res = op(d, va); + DemoteStoreN(d, r + x, res, remaining); + } +} + +/// Execute a binary SIMD operation on two arrays. +/// Processes array elements in SIMD batches, handling type promotion/demotion +/// and partial vectors at the end. +/// @param r Destination array +/// @param a First source array +/// @param b Second source array +/// @param n Number of elements to process +/// @param op Lambda/functor taking (descriptor, vector_a, vector_b) and returning result +/// Example: [](auto d, auto va, auto vb) { return hn::Add(va, vb); } +template +inline void +RunHwyCmd(Rtype* r, const Atype* a, const Btype* b, size_t n, OpFunc op) +{ + using MathT = typename SimdMathType::type; + const hn::ScalableTag d; + size_t x = 0; + size_t lanes = hn::Lanes(d); + for (; x + lanes <= n; x += lanes) { + auto va = LoadPromote(d, a + x); + auto vb = LoadPromote(d, b + x); + auto res = op(d, va, vb); + DemoteStore(d, r + x, res); + } + size_t remaining = n - x; + if (remaining > 0) { + auto va = LoadPromoteN(d, a + x, remaining); + auto vb = LoadPromoteN(d, b + x, remaining); + auto res = op(d, va, vb); + DemoteStoreN(d, r + x, res, remaining); + } +} + +/// Execute a ternary SIMD operation on three arrays. +/// Processes array elements in SIMD batches, handling type promotion/demotion +/// and partial vectors at the end. +/// @param r Destination array +/// @param a First source array +/// @param b Second source array +/// @param c Third source array +/// @param n Number of elements to process +/// @param op Lambda/functor taking (descriptor, vector_a, vector_b, vector_c) and returning result +/// Example: [](auto d, auto va, auto vb, auto vc) { return hn::MulAdd(va, vb, vc); } +template +inline void +RunHwyTernaryCmd(Rtype* r, const ABCtype* a, const ABCtype* b, const ABCtype* c, + size_t n, OpFunc op) +{ + using MathT = typename SimdMathType::type; + const hn::ScalableTag d; + size_t x = 0; + size_t lanes = hn::Lanes(d); + for (; x + lanes <= n; x += lanes) { + auto va = LoadPromote(d, a + x); + auto vb = LoadPromote(d, b + x); + auto vc = LoadPromote(d, c + x); + auto res = op(d, va, vb, vc); + DemoteStore(d, r + x, res); + } + size_t remaining = n - x; + if (remaining > 0) { + auto va = LoadPromoteN(d, a + x, remaining); + auto vb = LoadPromoteN(d, b + x, remaining); + auto vc = LoadPromoteN(d, c + x, remaining); + auto res = op(d, va, vb, vc); + DemoteStoreN(d, r + x, res, remaining); + } +} + +// ----------------------------------------------------------------------- +// Per-pixel Ops (ImageBufAlgo, contiguous interleaved channels) +// ----------------------------------------------------------------------- + +/// Execute a binary per-pixel HWY operation for interleaved, contiguous +/// channels. The caller is responsible for ensuring that the channel range is +/// contiguous for R/A/B (i.e. no per-pixel padding, and the ROI channel range +/// covers the full pixel). +template +inline bool +hwy_binary_perpixel_op(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, + ROI roi, int nthreads, OpFunc op) +{ + auto Rv = HwyPixels(R); + auto Av = HwyPixels(A); + auto Bv = HwyPixels(B); + ImageBufAlgo::parallel_image(roi, nthreads, [&, op](ROI roi) { + const int nchannels = RoiNChannels(roi); + const size_t n = static_cast(roi.width()) + * static_cast(nchannels); + for (int y = roi.ybegin; y < roi.yend; ++y) { + Rtype* r_row = RoiRowPtr(Rv, y, roi); + const Atype* a_row = RoiRowPtr(Av, y, roi); + const Btype* b_row = RoiRowPtr(Bv, y, roi); + RunHwyCmd(r_row, a_row, b_row, n, op); + } + }); + return true; +} + +/// Execute a ternary per-pixel HWY operation for interleaved, contiguous +/// channels. The caller is responsible for ensuring that the channel range is +/// contiguous for R/A/B/C (i.e. no per-pixel padding, and the ROI channel range +/// covers the full pixel). +template +inline bool +hwy_ternary_perpixel_op(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, + const ImageBuf& C, ROI roi, int nthreads, OpFunc op) +{ + auto Rv = HwyPixels(R); + auto Av = HwyPixels(A); + auto Bv = HwyPixels(B); + auto Cv = HwyPixels(C); + ImageBufAlgo::parallel_image(roi, nthreads, [&, op](ROI roi) { + const int nchannels = RoiNChannels(roi); + const size_t n = static_cast(roi.width()) + * static_cast(nchannels); + for (int y = roi.ybegin; y < roi.yend; ++y) { + Rtype* r_row = RoiRowPtr(Rv, y, roi); + const ABCtype* a_row = RoiRowPtr(Av, y, roi); + const ABCtype* b_row = RoiRowPtr(Bv, y, roi); + const ABCtype* c_row = RoiRowPtr(Cv, y, roi); + RunHwyTernaryCmd(r_row, a_row, b_row, c_row, n, op); + } + }); + return true; +} + +/// Execute a binary per-pixel HWY operation on native integer arrays (no type +/// promotion/normalization). The caller is responsible for ensuring that the +/// channel range is contiguous for R/A/B. +template +inline bool +hwy_binary_native_int_perpixel_op(ImageBuf& R, const ImageBuf& A, + const ImageBuf& B, ROI roi, int nthreads, + OpFunc op) +{ + auto Rv = HwyPixels(R); + auto Av = HwyPixels(A); + auto Bv = HwyPixels(B); + ImageBufAlgo::parallel_image(roi, nthreads, [&, op](ROI roi) { + const int nchannels = RoiNChannels(roi); + const size_t n = static_cast(roi.width()) + * static_cast(nchannels); + for (int y = roi.ybegin; y < roi.yend; ++y) { + T* r_row = RoiRowPtr(Rv, y, roi); + const T* a_row = RoiRowPtr(Av, y, roi); + const T* b_row = RoiRowPtr(Bv, y, roi); + RunHwyBinaryNativeInt(r_row, a_row, b_row, n, op); + } + }); + return true; +} + +// ----------------------------------------------------------------------- +// Per-pixel Ops (ImageBufAlgo, RGBA packed but ROI is RGB) +// ----------------------------------------------------------------------- + +/// Store only RGB results for `count` pixels into interleaved RGBA memory, +/// leaving alpha untouched. +template +inline void +StoreInterleaved3DemoteN(D d, DstT* ptr, VecT r, VecT g, VecT b, size_t count) +{ + DstT r_demoted[hn::MaxLanes(d)]; + DstT g_demoted[hn::MaxLanes(d)]; + DstT b_demoted[hn::MaxLanes(d)]; + DemoteStoreN(d, r_demoted, r, count); + DemoteStoreN(d, g_demoted, g, count); + DemoteStoreN(d, b_demoted, b, count); + for (size_t i = 0; i < count; ++i) { + ptr[i * 4 + 0] = r_demoted[i]; + ptr[i * 4 + 1] = g_demoted[i]; + ptr[i * 4 + 2] = b_demoted[i]; + // Preserve alpha (i*4+3). + } +} + +/// Execute a binary per-pixel HWY operation for the common "packed RGBA but +/// ROI is RGB" case. This can still SIMD by processing N pixels at a time and +/// passing alpha through unchanged from the destination. +template +inline bool +hwy_binary_perpixel_op_rgba_rgb_roi(ImageBuf& R, const ImageBuf& A, + const ImageBuf& B, ROI roi, int nthreads, + OpFunc op) +{ + if (roi.chbegin != 0 || roi.chend != 3) + return false; + + auto Rv = HwyPixels(R); + auto Av = HwyPixels(A); + auto Bv = HwyPixels(B); + if (Rv.nchannels < 4 || Av.nchannels < 4 || Bv.nchannels < 4) + return false; + + // Require packed RGBA (no padding, exactly 4 channels). + if (!ChannelsContiguous(Rv, 4) || !ChannelsContiguous(Av, 4) + || !ChannelsContiguous(Bv, 4)) + return false; + + ROI roi4 = roi; + roi4.chbegin = 0; + roi4.chend = 4; + + using MathT = typename SimdMathType::type; + const hn::ScalableTag d; + const size_t lanes = hn::Lanes(d); + + ImageBufAlgo::parallel_image(roi4, nthreads, [&, op](ROI roi4) { + for (int y = roi4.ybegin; y < roi4.yend; ++y) { + Rtype* r_row = RoiRowPtr(Rv, y, roi4); + const Atype* a_row = RoiRowPtr(Av, y, roi4); + const Btype* b_row = RoiRowPtr(Bv, y, roi4); + const size_t npixels = static_cast(roi4.width()); + + size_t x = 0; + for (; x + lanes <= npixels; x += lanes) { + const size_t off = x * 4; + + auto [ar, ag, ab, aa] = LoadInterleaved4Promote(d, a_row + off); + auto [br, bg, bb, ba] = LoadInterleaved4Promote(d, b_row + off); + (void)aa; + (void)ba; + + using DstLaneT = HwyLaneTypeT; + auto d_dstlane = hn::Rebind(); + hn::Vec dr, dg, db, da; + hn::LoadInterleaved4(d_dstlane, + reinterpret_cast(r_row + + off), + dr, dg, db, da); + (void)dr; + (void)dg; + (void)db; + + auto rr = op(d, ar, br); + auto rg = op(d, ag, bg); + auto rb = op(d, ab, bb); + StoreInterleaved4RgbAlphaPassthrough(d, r_row + off, rr, rg, rb, + da); + } + + const size_t remaining = npixels - x; + if (remaining > 0) { + const size_t off = x * 4; + auto [ar, ag, ab, aa] = LoadInterleaved4PromoteN(d, a_row + off, + remaining); + auto [br, bg, bb, ba] = LoadInterleaved4PromoteN(d, b_row + off, + remaining); + (void)aa; + (void)ba; + auto rr = op(d, ar, br); + auto rg = op(d, ag, bg); + auto rb = op(d, ab, bb); + StoreInterleaved3DemoteN(d, r_row + off, rr, rg, rb, remaining); + } + } + }); + + return true; +} + +/// Execute a ternary per-pixel HWY operation for the common "packed RGBA but +/// ROI is RGB" case, passing alpha through unchanged from the destination. +template +inline bool +hwy_ternary_perpixel_op_rgba_rgb_roi(ImageBuf& R, const ImageBuf& A, + const ImageBuf& B, const ImageBuf& C, + ROI roi, int nthreads, OpFunc op) +{ + if (roi.chbegin != 0 || roi.chend != 3) + return false; + + auto Rv = HwyPixels(R); + auto Av = HwyPixels(A); + auto Bv = HwyPixels(B); + auto Cv = HwyPixels(C); + if (Rv.nchannels < 4 || Av.nchannels < 4 || Bv.nchannels < 4 + || Cv.nchannels < 4) + return false; + + if (!ChannelsContiguous(Rv, 4) || !ChannelsContiguous(Av, 4) + || !ChannelsContiguous(Bv, 4) + || !ChannelsContiguous(Cv, 4)) + return false; + + ROI roi4 = roi; + roi4.chbegin = 0; + roi4.chend = 4; + + using MathT = typename SimdMathType::type; + const hn::ScalableTag d; + const size_t lanes = hn::Lanes(d); + + ImageBufAlgo::parallel_image(roi4, nthreads, [&, op](ROI roi4) { + for (int y = roi4.ybegin; y < roi4.yend; ++y) { + Rtype* r_row = RoiRowPtr(Rv, y, roi4); + const ABCtype* a_row = RoiRowPtr(Av, y, roi4); + const ABCtype* b_row = RoiRowPtr(Bv, y, roi4); + const ABCtype* c_row = RoiRowPtr(Cv, y, roi4); + const size_t npixels = static_cast(roi4.width()); + + size_t x = 0; + for (; x + lanes <= npixels; x += lanes) { + const size_t off = x * 4; + + auto [ar, ag, ab, aa] = LoadInterleaved4Promote(d, a_row + off); + auto [br, bg, bb, ba] = LoadInterleaved4Promote(d, b_row + off); + auto [cr, cg, cb, ca] = LoadInterleaved4Promote(d, c_row + off); + (void)aa; + (void)ba; + (void)ca; + + using DstLaneT = HwyLaneTypeT; + auto d_dstlane = hn::Rebind(); + hn::Vec dr, dg, db, da; + hn::LoadInterleaved4(d_dstlane, + reinterpret_cast(r_row + + off), + dr, dg, db, da); + (void)dr; + (void)dg; + (void)db; + + auto rr = op(d, ar, br, cr); + auto rg = op(d, ag, bg, cg); + auto rb = op(d, ab, bb, cb); + StoreInterleaved4RgbAlphaPassthrough(d, r_row + off, rr, rg, rb, + da); + } + + const size_t remaining = npixels - x; + if (remaining > 0) { + const size_t off = x * 4; + auto [ar, ag, ab, aa] = LoadInterleaved4PromoteN(d, a_row + off, + remaining); + auto [br, bg, bb, ba] = LoadInterleaved4PromoteN(d, b_row + off, + remaining); + auto [cr, cg, cb, ca] = LoadInterleaved4PromoteN(d, c_row + off, + remaining); + (void)aa; + (void)ba; + (void)ca; + auto rr = op(d, ar, br, cr); + auto rg = op(d, ag, bg, cg); + auto rb = op(d, ab, bb, cb); + StoreInterleaved3DemoteN(d, r_row + off, rr, rg, rb, remaining); + } + } + }); + + return true; +} + +/// Execute a native integer binary operation for the "packed RGBA but ROI is +/// RGB" case. This operates on the raw integer channel values and preserves +/// alpha by masking. +template +inline bool +hwy_binary_native_int_perpixel_op_rgba_rgb_roi(ImageBuf& R, const ImageBuf& A, + const ImageBuf& B, ROI roi, + int nthreads, OpFunc op) +{ + if (roi.chbegin != 0 || roi.chend != 3) + return false; + + auto Rv = HwyPixels(R); + auto Av = HwyPixels(A); + auto Bv = HwyPixels(B); + if (Rv.nchannels < 4 || Av.nchannels < 4 || Bv.nchannels < 4) + return false; + if (!ChannelsContiguous(Rv, 4) || !ChannelsContiguous(Av, 4) + || !ChannelsContiguous(Bv, 4)) + return false; + + const hn::ScalableTag d; + const size_t lanes = hn::Lanes(d); + if (lanes & 3) + return false; // need block alignment so the alpha mask stays aligned + + const auto three = hn::Set(d, T(3)); + const auto lane_index = hn::Iota(d, T(0)); + const auto alpha_mask = hn::Eq(hn::And(lane_index, three), three); + + ROI roi4 = roi; + roi4.chbegin = 0; + roi4.chend = 4; + ImageBufAlgo::parallel_image(roi4, nthreads, [&, op](ROI roi4) { + const size_t n = static_cast(roi4.width()) * 4; + for (int y = roi4.ybegin; y < roi4.yend; ++y) { + T* r_row = RoiRowPtr(Rv, y, roi4); + const T* a_row = RoiRowPtr(Av, y, roi4); + const T* b_row = RoiRowPtr(Bv, y, roi4); + size_t i = 0; + for (; i + lanes <= n; i += lanes) { + auto va = hn::Load(d, a_row + i); + auto vb = hn::Load(d, b_row + i); + auto vold = hn::Load(d, r_row + i); + auto vnew = op(d, va, vb); + auto vres = hn::IfThenElse(alpha_mask, vold, vnew); + hn::Store(vres, d, r_row + i); + } + const size_t remaining = n - i; + if (remaining > 0) { + auto va = hn::LoadN(d, a_row + i, remaining); + auto vb = hn::LoadN(d, b_row + i, remaining); + auto vold = hn::LoadN(d, r_row + i, remaining); + auto vnew = op(d, va, vb); + auto vres = hn::IfThenElse(alpha_mask, vold, vnew); + hn::StoreN(vres, d, r_row + i, remaining); + } + } + }); + + return true; +} + +// ----------------------------------------------------------------------- +// Interleaved Channel Load/Store Helpers +// ----------------------------------------------------------------------- + +/// Load 4 interleaved channels (RGBA) with type promotion. +/// For matching types, uses Highway's native LoadInterleaved4. +/// For type promotion, loads and manually deinterleaves. +/// @param d Highway descriptor tag for the target SIMD type +/// @param ptr Pointer to interleaved RGBA data (R0,G0,B0,A0,R1,G1,B1,A1,...) +/// @return Tuple of (R, G, B, A) SIMD vectors in promoted type +template +inline std::tuple, hn::Vec, hn::Vec, hn::Vec> +LoadInterleaved4Promote(D d, const SrcT* ptr) +{ + using MathT = typename D::T; + using Vec = hn::Vec; + + if constexpr (std::is_same_v) { + // No promotion needed - use Highway's optimized LoadInterleaved4 + Vec r, g, b, a; + hn::LoadInterleaved4(d, ptr, r, g, b, a); + return std::make_tuple(r, g, b, a); + } else if constexpr (std::is_same_v) { + // Special handling for half type - convert through hwy::float16_t + using T16 = hwy::float16_t; + auto d16 = hn::Rebind(); + + // Load interleaved half data as float16_t + hn::Vec r16, g16, b16, a16; + hn::LoadInterleaved4(d16, reinterpret_cast(ptr), r16, g16, + b16, a16); + + // Promote to computation type + Vec r_vec = hn::PromoteTo(d, r16); + Vec g_vec = hn::PromoteTo(d, g16); + Vec b_vec = hn::PromoteTo(d, b16); + Vec a_vec = hn::PromoteTo(d, a16); + + return std::make_tuple(r_vec, g_vec, b_vec, a_vec); + } else { + // Generic type promotion - deinterleave with HWY then promote. + auto d_src = hn::Rebind(); + hn::Vec r_src, g_src, b_src, a_src; + hn::LoadInterleaved4(d_src, ptr, r_src, g_src, b_src, a_src); + auto r_vec = PromoteVec(d, r_src); + auto g_vec = PromoteVec(d, g_src); + auto b_vec = PromoteVec(d, b_src); + auto a_vec = PromoteVec(d, a_src); + return std::make_tuple(r_vec, g_vec, b_vec, a_vec); + } +} + +/// Load 4 interleaved channels (RGBA) with type promotion for a partial vector +/// (count pixels, count <= lanes). +template +inline std::tuple, hn::Vec, hn::Vec, hn::Vec> +LoadInterleaved4PromoteN(D d, const SrcT* ptr, size_t count) +{ + SrcT r_src[hn::MaxLanes(d)]; + SrcT g_src[hn::MaxLanes(d)]; + SrcT b_src[hn::MaxLanes(d)]; + SrcT a_src[hn::MaxLanes(d)]; + for (size_t i = 0; i < count; ++i) { + r_src[i] = ptr[i * 4 + 0]; + g_src[i] = ptr[i * 4 + 1]; + b_src[i] = ptr[i * 4 + 2]; + a_src[i] = ptr[i * 4 + 3]; + } + auto r_vec = LoadPromoteN(d, r_src, count); + auto g_vec = LoadPromoteN(d, g_src, count); + auto b_vec = LoadPromoteN(d, b_src, count); + auto a_vec = LoadPromoteN(d, a_src, count); + return std::make_tuple(r_vec, g_vec, b_vec, a_vec); +} + +/// Store 4 interleaved channels (RGBA) with type demotion. +/// For matching types, uses Highway's native StoreInterleaved4. +/// For type demotion, manually interleaves and stores. +/// @param d Highway descriptor tag for the source SIMD type +/// @param ptr Pointer to destination interleaved RGBA data +/// @param r Red channel SIMD vector +/// @param g Green channel SIMD vector +/// @param b Blue channel SIMD vector +/// @param a Alpha channel SIMD vector +template +inline void +StoreInterleaved4Demote(D d, DstT* ptr, VecT r, VecT g, VecT b, VecT a) +{ + using MathT = typename D::T; + + if constexpr (std::is_same_v) { + // No demotion needed - use Highway's optimized StoreInterleaved4 + hn::StoreInterleaved4(r, g, b, a, d, ptr); + } else if constexpr (std::is_same_v) { + // Special handling for half type - convert through hwy::float16_t + using T16 = hwy::float16_t; + auto d16 = hn::Rebind(); + + // Demote to float16_t + auto r16 = hn::DemoteTo(d16, r); + auto g16 = hn::DemoteTo(d16, g); + auto b16 = hn::DemoteTo(d16, b); + auto a16 = hn::DemoteTo(d16, a); + + // Store interleaved float16_t data + hn::StoreInterleaved4(r16, g16, b16, a16, d16, + reinterpret_cast(ptr)); + } else { + // Generic type demotion - demote to lane vectors and use HWY's interleaved store. + auto d_dst = hn::Rebind(); + auto r_dst = DemoteVec(d, r); + auto g_dst = DemoteVec(d, g); + auto b_dst = DemoteVec(d, b); + auto a_dst = DemoteVec(d, a); + hn::StoreInterleaved4(r_dst, g_dst, b_dst, a_dst, d_dst, ptr); + } +} + +/// Store 4 interleaved channels (RGBA) demoting RGB from math type and passing +/// alpha through unchanged (alpha is already in the destination lane type). +template +inline void +StoreInterleaved4RgbAlphaPassthrough(D d, DstT* ptr, VecMathT r, VecMathT g, + VecMathT b, VecAlphaLaneT a_passthrough) +{ + using MathT = typename D::T; + if constexpr (std::is_same_v) { + hn::StoreInterleaved4(r, g, b, a_passthrough, d, ptr); + } else if constexpr (std::is_same_v) { + using T16 = hwy::float16_t; + auto d16 = hn::Rebind(); + auto r16 = hn::DemoteTo(d16, r); + auto g16 = hn::DemoteTo(d16, g); + auto b16 = hn::DemoteTo(d16, b); + hn::StoreInterleaved4(r16, g16, b16, a_passthrough, d16, + reinterpret_cast(ptr)); + } else { + auto d_dst = hn::Rebind(); + auto r_dst = DemoteVec(d, r); + auto g_dst = DemoteVec(d, g); + auto b_dst = DemoteVec(d, b); + hn::StoreInterleaved4(r_dst, g_dst, b_dst, a_passthrough, d_dst, ptr); + } +} + +// ----------------------------------------------------------------------- +// Rangecompress/Rangeexpand SIMD Kernels +// ----------------------------------------------------------------------- + +/// Apply rangecompress formula to a SIMD vector. +/// Formula (courtesy Sony Pictures Imageworks): +/// if (|x| <= 0.18) return x +/// else return copysign(a + b * log(c * |x| + 1), x) +/// where a = -0.545768857, b = 0.183516696, c = 284.357788 +/// @param d Highway descriptor tag +/// @param x Input SIMD vector +/// @return Compressed SIMD vector +template +inline auto +rangecompress_simd(D d, VecT x) +{ + using T = typename D::T; + + // Constants from Sony Pictures Imageworks + constexpr T x1 = static_cast(0.18); + constexpr T a = static_cast(-0.54576885700225830078); + constexpr T b = static_cast(0.18351669609546661377); + constexpr T c = static_cast(284.3577880859375); + + auto abs_x = hn::Abs(x); + auto mask_passthrough = hn::Le(abs_x, hn::Set(d, x1)); + + // compressed = a + b * log(c * |x| + 1.0) + auto c_vec = hn::Set(d, c); + auto one = hn::Set(d, static_cast(1.0)); + auto temp = hn::MulAdd(c_vec, abs_x, one); // c * |x| + 1.0 + auto log_val = hn::Log(d, temp); + auto b_vec = hn::Set(d, b); + auto a_vec = hn::Set(d, a); + auto compressed = hn::MulAdd(b_vec, log_val, a_vec); // a + b * log + + // Apply sign of original x + auto result = hn::CopySign(compressed, x); + + // If |x| <= x1, return x; else return compressed + return hn::IfThenElse(mask_passthrough, x, result); +} + +/// Apply rangeexpand formula to a SIMD vector (inverse of rangecompress). +/// Formula: +/// if (|y| <= 0.18) return y +/// else x = exp((|y| - a) / b); x = (x - 1) / c +/// if x < 0.18 then x = (-x_intermediate - 1) / c +/// return copysign(x, y) +/// @param d Highway descriptor tag +/// @param y Input SIMD vector (compressed values) +/// @return Expanded SIMD vector +template +inline auto +rangeexpand_simd(D d, VecT y) +{ + using T = typename D::T; + + // Constants (same as rangecompress) + constexpr T x1 = static_cast(0.18); + constexpr T a = static_cast(-0.54576885700225830078); + constexpr T b = static_cast(0.18351669609546661377); + constexpr T c = static_cast(284.3577880859375); + + auto abs_y = hn::Abs(y); + auto mask_passthrough = hn::Le(abs_y, hn::Set(d, x1)); + + // x_intermediate = exp((|y| - a) / b) + auto a_vec = hn::Set(d, a); + auto b_vec = hn::Set(d, b); + auto intermediate = hn::Div(hn::Sub(abs_y, a_vec), b_vec); // (|y| - a) / b + auto x_intermediate = hn::Exp(d, intermediate); + + // x = (x_intermediate - 1.0) / c + auto one = hn::Set(d, static_cast(1.0)); + auto c_vec = hn::Set(d, c); + auto x = hn::Div(hn::Sub(x_intermediate, one), c_vec); + + // If x < x1, use alternate solution: (-x_intermediate - 1.0) / c + auto mask_alternate = hn::Lt(x, hn::Set(d, x1)); + auto x_alternate = hn::Div(hn::Sub(hn::Neg(x_intermediate), one), c_vec); + x = hn::IfThenElse(mask_alternate, x_alternate, x); + + // Apply sign of input y + auto result = hn::CopySign(x, y); + + return hn::IfThenElse(mask_passthrough, y, result); +} + +OIIO_NAMESPACE_END diff --git a/src/libOpenImageIO/imagebufalgo_mad.cpp b/src/libOpenImageIO/imagebufalgo_mad.cpp index 5707fcd6ac..30ad86e9e1 100644 --- a/src/libOpenImageIO/imagebufalgo_mad.cpp +++ b/src/libOpenImageIO/imagebufalgo_mad.cpp @@ -6,12 +6,19 @@ #include #include +#if defined(_WIN32) +# include // for alloca +#endif + #include #include #include #include #include +#if defined(OIIO_USE_HWY) && OIIO_USE_HWY +# include "imagebufalgo_hwy_pvt.h" +#endif #include "imageio_pvt.h" @@ -21,62 +28,17 @@ OIIO_NAMESPACE_3_1_BEGIN template static bool -mad_impl(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, const ImageBuf& C, - ROI roi, int nthreads) +mad_impl_scalar(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, + const ImageBuf& C, ROI roi, int nthreads) { ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) { - if ((std::is_same::value - || std::is_same::value) - && (std::is_same::value - || std::is_same::value) - // && R.localpixels() // has to be, because it's writable - && A.localpixels() && B.localpixels() - && C.localpixels() - // && R.contains_roi(roi) // has to be, because IBAPrep - && A.contains_roi(roi) && B.contains_roi(roi) && C.contains_roi(roi) - && roi.chbegin == 0 && roi.chend == R.nchannels() - && roi.chend == A.nchannels() && roi.chend == B.nchannels() - && roi.chend == C.nchannels()) { - // Special case when all inputs are either float or half, with in- - // memory contiguous data and we're operating on the full channel - // range: skip iterators: For these circumstances, we can operate on - // the raw memory very efficiently. Otherwise, we will need the - // magic of the the Iterators (and pay the price). - int nxvalues = roi.width() * R.nchannels(); - for (int z = roi.zbegin; z < roi.zend; ++z) - for (int y = roi.ybegin; y < roi.yend; ++y) { - Rtype* rraw = (Rtype*)R.pixeladdr(roi.xbegin, y, z); - const ABCtype* araw - = (const ABCtype*)A.pixeladdr(roi.xbegin, y, z); - const ABCtype* braw - = (const ABCtype*)B.pixeladdr(roi.xbegin, y, z); - const ABCtype* craw - = (const ABCtype*)C.pixeladdr(roi.xbegin, y, z); - OIIO_DASSERT(araw && braw && craw); - // The straightforward loop auto-vectorizes very well, - // there's no benefit to using explicit SIMD here. - for (int x = 0; x < nxvalues; ++x) - rraw[x] = araw[x] * braw[x] + craw[x]; - // But if you did want to explicitly vectorize, this is - // how it would look: - // int simdend = nxvalues & (~3); // how many float4's? - // for (int x = 0; x < simdend; x += 4) { - // simd::float4 a_simd(araw+x), b_simd(braw+x), c_simd(craw+x); - // simd::float4 r_simd = a_simd * b_simd + c_simd; - // r_simd.store (rraw+x); - // } - // for (int x = simdend; x < nxvalues; ++x) - // rraw[x] = araw[x] * braw[x] + craw[x]; - } - } else { - ImageBuf::Iterator r(R, roi); - ImageBuf::ConstIterator a(A, roi); - ImageBuf::ConstIterator b(B, roi); - ImageBuf::ConstIterator c(C, roi); - for (; !r.done(); ++r, ++a, ++b, ++c) { - for (int ch = roi.chbegin; ch < roi.chend; ++ch) - r[ch] = a[ch] * b[ch] + c[ch]; - } + ImageBuf::Iterator r(R, roi); + ImageBuf::ConstIterator a(A, roi); + ImageBuf::ConstIterator b(B, roi); + ImageBuf::ConstIterator c(C, roi); + for (; !r.done(); ++r, ++a, ++b, ++c) { + for (int ch = roi.chbegin; ch < roi.chend; ++ch) + r[ch] = a[ch] * b[ch] + c[ch]; } }); return true; @@ -84,6 +46,63 @@ mad_impl(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, const ImageBuf& C, +#if defined(OIIO_USE_HWY) && OIIO_USE_HWY +template +static bool +mad_impl_hwy(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, + const ImageBuf& C, ROI roi, int nthreads) +{ + auto op = [](auto /*d*/, auto a, auto b, auto c) { + return hn::MulAdd(a, b, c); + }; + + if (hwy_ternary_perpixel_op_rgba_rgb_roi(R, A, B, C, roi, + nthreads, op)) + return true; + + return hwy_ternary_perpixel_op(R, A, B, C, roi, nthreads, + op); +} +#endif // defined(OIIO_USE_HWY) && OIIO_USE_HWY + +template +static bool +mad_impl(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, const ImageBuf& C, + ROI roi, int nthreads) +{ +#if defined(OIIO_USE_HWY) && OIIO_USE_HWY + if (OIIO::pvt::enable_hwy && R.localpixels() && A.localpixels() + && B.localpixels() && C.localpixels()) { + auto Rv = HwyPixels(R); + auto Av = HwyPixels(A); + auto Bv = HwyPixels(B); + auto Cv = HwyPixels(C); + const int nchannels = RoiNChannels(roi); + const bool contig = ChannelsContiguous(Rv, nchannels) + && ChannelsContiguous(Av, nchannels) + && ChannelsContiguous(Bv, nchannels) + && ChannelsContiguous(Cv, nchannels); + if (contig) + return mad_impl_hwy(R, A, B, C, roi, nthreads); + + // Handle the common RGBA + RGB ROI strided case (preserving alpha). + if (roi.chbegin == 0 && roi.chend == 3) { + const bool contig4 = (Rv.nchannels >= 4 && Av.nchannels >= 4 + && Bv.nchannels >= 4 && Cv.nchannels >= 4) + && ChannelsContiguous(Rv, 4) + && ChannelsContiguous(Av, 4) + && ChannelsContiguous(Bv, 4) + && ChannelsContiguous(Cv, 4); + if (contig4) + return mad_impl_hwy(R, A, B, C, roi, nthreads); + } + } +#endif + return mad_impl_scalar(R, A, B, C, roi, nthreads); +} + + + template static bool mad_impl_ici(ImageBuf& R, const ImageBuf& A, cspan b, const ImageBuf& C, diff --git a/src/libOpenImageIO/imagebufalgo_muldiv.cpp b/src/libOpenImageIO/imagebufalgo_muldiv.cpp index 4fa1a6cba0..97d2c94faf 100644 --- a/src/libOpenImageIO/imagebufalgo_muldiv.cpp +++ b/src/libOpenImageIO/imagebufalgo_muldiv.cpp @@ -10,8 +10,15 @@ #include #include +#if defined(_WIN32) +# include // for alloca +#endif + #include +#if defined(OIIO_USE_HWY) && OIIO_USE_HWY +# include "imagebufalgo_hwy_pvt.h" +#endif #include #include #include @@ -86,8 +93,8 @@ ImageBufAlgo::scale(const ImageBuf& A, const ImageBuf& B, KWArgs options, template static bool -mul_impl(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, ROI roi, - int nthreads) +mul_impl_scalar(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, ROI roi, + int nthreads) { ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) { ImageBuf::Iterator r(R, roi); @@ -104,7 +111,8 @@ mul_impl(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, ROI roi, template static bool -mul_impl(ImageBuf& R, const ImageBuf& A, cspan b, ROI roi, int nthreads) +mul_impl_scalar(ImageBuf& R, const ImageBuf& A, cspan b, ROI roi, + int nthreads) { ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) { ImageBuf::ConstIterator a(A, roi); @@ -117,6 +125,96 @@ mul_impl(ImageBuf& R, const ImageBuf& A, cspan b, ROI roi, int nthreads) +#if defined(OIIO_USE_HWY) && OIIO_USE_HWY +template +static bool +mul_impl_hwy(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, ROI roi, + int nthreads) +{ + auto op = [](auto /*d*/, auto a, auto b) { return hn::Mul(a, b); }; + + if (hwy_binary_perpixel_op_rgba_rgb_roi(R, A, B, roi, + nthreads, op)) + return true; + + return hwy_binary_perpixel_op(R, A, B, roi, nthreads, + op); +} + +template +static bool +mul_impl_hwy(ImageBuf& R, const ImageBuf& A, cspan b, ROI roi, + int nthreads) +{ + using SimdType + = std::conditional_t, double, float>; + auto Rv = HwyPixels(R); + auto Av = HwyPixels(A); + ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) { + for (int y = roi.ybegin; y < roi.yend; ++y) { + std::byte* r_row = PixelBase(Rv, roi.xbegin, y); + const std::byte* a_row = PixelBase(Av, roi.xbegin, y); + for (int x = roi.xbegin; x < roi.xend; ++x) { + const size_t xoff = static_cast(x - roi.xbegin); + Rtype* r_ptr = reinterpret_cast( + r_row + xoff * Rv.pixel_bytes); + const Atype* a_ptr = reinterpret_cast( + a_row + xoff * Av.pixel_bytes); + for (int c = roi.chbegin; c < roi.chend; ++c) { + r_ptr[c] = (Rtype)((SimdType)a_ptr[c] * (SimdType)b[c]); + } + } + } + }); + return true; +} +#endif // defined(OIIO_USE_HWY) && OIIO_USE_HWY + +template +static bool +mul_impl(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, ROI roi, + int nthreads) +{ +#if defined(OIIO_USE_HWY) && OIIO_USE_HWY + if (OIIO::pvt::enable_hwy && R.localpixels() && A.localpixels() + && B.localpixels()) { + auto Rv = HwyPixels(R); + auto Av = HwyPixels(A); + auto Bv = HwyPixels(B); + const int nchannels = RoiNChannels(roi); + const bool contig = ChannelsContiguous(Rv, nchannels) + && ChannelsContiguous(Av, nchannels) + && ChannelsContiguous(Bv, nchannels); + if (contig) + return mul_impl_hwy(R, A, B, roi, nthreads); + + // Handle the common RGBA + RGB ROI strided case (preserving alpha). + if (roi.chbegin == 0 && roi.chend == 3) { + const bool contig4 = (Rv.nchannels >= 4 && Av.nchannels >= 4 + && Bv.nchannels >= 4) + && ChannelsContiguous(Rv, 4) + && ChannelsContiguous(Av, 4) + && ChannelsContiguous(Bv, 4); + if (contig4) + return mul_impl_hwy(R, A, B, roi, + nthreads); + } + } +#endif + return mul_impl_scalar(R, A, B, roi, nthreads); +} + +template +static bool +mul_impl(ImageBuf& R, const ImageBuf& A, cspan b, ROI roi, int nthreads) +{ +#if defined(OIIO_USE_HWY) && OIIO_USE_HWY + if (OIIO::pvt::enable_hwy && R.localpixels() && A.localpixels()) + return mul_impl_hwy(R, A, b, roi, nthreads); +#endif + return mul_impl_scalar(R, A, b, roi, nthreads); +} + static bool mul_impl_deep(ImageBuf& R, const ImageBuf& A, cspan b, ROI roi, int nthreads) @@ -198,8 +296,8 @@ ImageBufAlgo::mul(Image_or_Const A, Image_or_Const B, ROI roi, int nthreads) template static bool -div_impl(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, ROI roi, - int nthreads) +div_impl_scalar(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, ROI roi, + int nthreads) { ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) { ImageBuf::Iterator r(R, roi); @@ -216,6 +314,66 @@ div_impl(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, ROI roi, +#if defined(OIIO_USE_HWY) && OIIO_USE_HWY +template +static bool +div_impl_hwy(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, ROI roi, + int nthreads) +{ + auto op = [](auto d, auto a, auto b) { + const auto zero = hn::Zero(d); + const auto nz = hn::Ne(b, zero); + const auto one = hn::Set(d, 1); + const auto safe_b = hn::IfThenElse(nz, b, one); + const auto q = hn::Div(a, safe_b); + return hn::IfThenElse(nz, q, zero); + }; + + if (hwy_binary_perpixel_op_rgba_rgb_roi(R, A, B, roi, + nthreads, op)) + return true; + + return hwy_binary_perpixel_op(R, A, B, roi, nthreads, + op); +} +#endif // defined(OIIO_USE_HWY) && OIIO_USE_HWY + +template +static bool +div_impl(ImageBuf& R, const ImageBuf& A, const ImageBuf& B, ROI roi, + int nthreads) +{ +#if defined(OIIO_USE_HWY) && OIIO_USE_HWY + if (OIIO::pvt::enable_hwy && R.localpixels() && A.localpixels() + && B.localpixels()) { + auto Rv = HwyPixels(R); + auto Av = HwyPixels(A); + auto Bv = HwyPixels(B); + const int nchannels = RoiNChannels(roi); + const bool contig = ChannelsContiguous(Rv, nchannels) + && ChannelsContiguous(Av, nchannels) + && ChannelsContiguous(Bv, nchannels); + if (contig) + return div_impl_hwy(R, A, B, roi, nthreads); + + // Handle the common RGBA + RGB ROI strided case (preserving alpha). + if (roi.chbegin == 0 && roi.chend == 3) { + const bool contig4 = (Rv.nchannels >= 4 && Av.nchannels >= 4 + && Bv.nchannels >= 4) + && ChannelsContiguous(Rv, 4) + && ChannelsContiguous(Av, 4) + && ChannelsContiguous(Bv, 4); + if (contig4) + return div_impl_hwy(R, A, B, roi, + nthreads); + } + } +#endif + return div_impl_scalar(R, A, B, roi, nthreads); +} + + + bool ImageBufAlgo::div(ImageBuf& dst, Image_or_Const A_, Image_or_Const B_, ROI roi, int nthreads) diff --git a/src/libOpenImageIO/imagebufalgo_test.cpp b/src/libOpenImageIO/imagebufalgo_test.cpp index 940e2a8ff5..74f798c31a 100644 --- a/src/libOpenImageIO/imagebufalgo_test.cpp +++ b/src/libOpenImageIO/imagebufalgo_test.cpp @@ -511,6 +511,86 @@ test_mad() +void +test_hwy_strided_roi_fallback() +{ +#if defined(OIIO_USE_HWY) && OIIO_USE_HWY + std::cout << "test hwy strided roi fallback\n"; + + int prev_enable_hwy = 0; + OIIO::getattribute("enable_hwy", prev_enable_hwy); + + ImageSpec spec(64, 64, 4, TypeDesc::UINT8); + ImageBuf A(spec), B(spec), C(spec); + ImageBufAlgo::fill(A, { 0.2f, 0.4f, 0.6f, 0.8f }); + ImageBufAlgo::fill(B, { 0.1f, 0.3f, 0.5f, 0.7f }); + ImageBufAlgo::fill(C, { 0.05f, 0.05f, 0.05f, 0.05f }); + + ROI roi = get_roi(A.spec()); + roi.chbegin = 0; + roi.chend = 3; // RGB only => non-contiguous for RGBA interleaving + + { + ImageBuf R0(spec), R1(spec); + ImageBufAlgo::fill(R0, { 0.9f, 0.8f, 0.7f, 0.6f }); + ImageBufAlgo::fill(R1, { 0.9f, 0.8f, 0.7f, 0.6f }); + OIIO::attribute("enable_hwy", 0); + ImageBufAlgo::add(R0, A, B, roi); + OIIO::attribute("enable_hwy", 1); + ImageBufAlgo::add(R1, A, B, roi); + auto comp = ImageBufAlgo::compare(R0, R1, 0.0f, 0.0f); + OIIO_CHECK_EQUAL(comp.maxerror, 0.0f); + } + { + ImageBuf R0(spec), R1(spec); + ImageBufAlgo::fill(R0, { 0.9f, 0.8f, 0.7f, 0.6f }); + ImageBufAlgo::fill(R1, { 0.9f, 0.8f, 0.7f, 0.6f }); + OIIO::attribute("enable_hwy", 0); + ImageBufAlgo::sub(R0, A, B, roi); + OIIO::attribute("enable_hwy", 1); + ImageBufAlgo::sub(R1, A, B, roi); + auto comp = ImageBufAlgo::compare(R0, R1, 0.0f, 0.0f); + OIIO_CHECK_EQUAL(comp.maxerror, 0.0f); + } + { + ImageBuf R0(spec), R1(spec); + ImageBufAlgo::fill(R0, { 0.9f, 0.8f, 0.7f, 0.6f }); + ImageBufAlgo::fill(R1, { 0.9f, 0.8f, 0.7f, 0.6f }); + OIIO::attribute("enable_hwy", 0); + ImageBufAlgo::mul(R0, A, B, roi); + OIIO::attribute("enable_hwy", 1); + ImageBufAlgo::mul(R1, A, B, roi); + auto comp = ImageBufAlgo::compare(R0, R1, 0.0f, 0.0f); + OIIO_CHECK_EQUAL(comp.maxerror, 0.0f); + } + { + ImageBuf R0(spec), R1(spec); + ImageBufAlgo::fill(R0, { 0.9f, 0.8f, 0.7f, 0.6f }); + ImageBufAlgo::fill(R1, { 0.9f, 0.8f, 0.7f, 0.6f }); + OIIO::attribute("enable_hwy", 0); + ImageBufAlgo::div(R0, A, B, roi); + OIIO::attribute("enable_hwy", 1); + ImageBufAlgo::div(R1, A, B, roi); + auto comp = ImageBufAlgo::compare(R0, R1, 0.0f, 0.0f); + OIIO_CHECK_EQUAL(comp.maxerror, 0.0f); + } + { + ImageBuf R0(spec), R1(spec); + ImageBufAlgo::fill(R0, { 0.9f, 0.8f, 0.7f, 0.6f }); + ImageBufAlgo::fill(R1, { 0.9f, 0.8f, 0.7f, 0.6f }); + OIIO::attribute("enable_hwy", 0); + ImageBufAlgo::mad(R0, A, B, C, roi); + OIIO::attribute("enable_hwy", 1); + ImageBufAlgo::mad(R1, A, B, C, roi); + auto comp = ImageBufAlgo::compare(R0, R1, 0.0f, 0.0f); + OIIO_CHECK_EQUAL(comp.maxerror, 0.0f); + } + + OIIO::attribute("enable_hwy", prev_enable_hwy); +#endif +} + + // Tests ImageBufAlgo::min void test_min() @@ -641,6 +721,41 @@ test_zover() +// Test ImageBuf::resample +void +test_resample() +{ + std::cout << "test resample\n"; + + // Timing + Benchmarker bench; + bench.units(Benchmarker::Unit::ms); + + ImageSpec spec_hd_rgba_f(1920, 1080, 4, TypeFloat); + ImageSpec spec_hd_rgba_u8(1920, 1080, 4, TypeUInt8); + ImageBuf buf_hd_rgba_f(spec_hd_rgba_f); + ImageBuf buf_hd_rgba_u8(spec_hd_rgba_u8); + float red_rgba[] = { 1.0, 0.0, 0.0, 1.0 }; + ImageBufAlgo::fill(buf_hd_rgba_f, red_rgba); + ImageBufAlgo::fill(buf_hd_rgba_u8, red_rgba); + ImageBuf smallf(ImageSpec(1024, 512, 4, TypeFloat)); + ImageBuf smallu8(ImageSpec(1024, 512, 4, TypeUInt8)); + bench(" IBA::resample HD->1024x512 rgba f->f interp ", + [&]() { ImageBufAlgo::resample(smallf, buf_hd_rgba_f, true); }); + bench(" IBA::resample HD->1024x512 rgba f->u8 interp ", + [&]() { ImageBufAlgo::resample(smallu8, buf_hd_rgba_f, true); }); + bench(" IBA::resample HD->1024x512 rgba u8->u8 interp ", + [&]() { ImageBufAlgo::resample(smallu8, buf_hd_rgba_u8, true); }); + bench(" IBA::resample HD->1024x512 rgba f->f no interp ", + [&]() { ImageBufAlgo::resample(smallf, buf_hd_rgba_f, false); }); + bench(" IBA::resample HD->1024x512 rgba f->u8 no interp ", + [&]() { ImageBufAlgo::resample(smallu8, buf_hd_rgba_f, false); }); + bench(" IBA::resample HD->1024x512 rgba u8->u8 no interp ", + [&]() { ImageBufAlgo::resample(smallu8, buf_hd_rgba_u8, false); }); +} + + + // Tests ImageBufAlgo::compare void test_compare() @@ -1576,11 +1691,13 @@ main(int argc, char** argv) test_sub(); test_mul(); test_mad(); + test_hwy_strided_roi_fallback(); test_min(); test_max(); test_over(TypeFloat); test_over(TypeHalf); test_zover(); + test_resample(); test_compare(); test_isConstantColor(); test_isConstantChannel(); diff --git a/src/libOpenImageIO/imagebufalgo_xform.cpp b/src/libOpenImageIO/imagebufalgo_xform.cpp index 0abbb1ace8..7e06e0cbcc 100644 --- a/src/libOpenImageIO/imagebufalgo_xform.cpp +++ b/src/libOpenImageIO/imagebufalgo_xform.cpp @@ -21,6 +21,10 @@ #include +#if defined(OIIO_USE_HWY) && OIIO_USE_HWY +# include "imagebufalgo_hwy_pvt.h" +#endif + OIIO_NAMESPACE_3_1_BEGIN @@ -1070,17 +1074,70 @@ ImageBufAlgo::fit(const ImageBuf& src, KWArgs options, ROI roi, int nthreads) +// This operates just like the internals of ImageBuf::interppixel(), but +// reuses the provided iterator to avoid the overhead of constructing a new +// one each time. This speeds it up by 20x! The iterator `it` must already be +// associated with `img`, but it need not be positioned correctly. +template +static bool +interppixel(const ImageBuf& img, ImageBuf::ConstIterator& it, float x, + float y, span pixel, ImageBuf::WrapMode wrap) +{ + int n = std::min(int(pixel.size()), img.spec().nchannels); + float* localpixel = OIIO_ALLOCA(float, n * 4); + float* p[4] = { localpixel, localpixel + n, localpixel + 2 * n, + localpixel + 3 * n }; + x -= 0.5f; + y -= 0.5f; + int xtexel, ytexel; + float xfrac, yfrac; + xfrac = floorfrac(x, &xtexel); + yfrac = floorfrac(y, &ytexel); + it.rerange(xtexel, xtexel + 2, ytexel, ytexel + 2, 0, 1, wrap); + for (int i = 0; i < 4; ++i, ++it) + for (int c = 0; c < n; ++c) + p[i][c] = it[c]; //NOSONAR + bilerp(p[0], p[1], p[2], p[3], xfrac, yfrac, n, pixel.data()); + return true; +} + + + template static bool -resample_(ImageBuf& dst, const ImageBuf& src, bool interpolate, ROI roi, - int nthreads) +resample_scalar(ImageBuf& dst, const ImageBuf& src, bool interpolate, ROI roi, + int nthreads) { + // This operates just like the internals of ImageBuf::interppixel(), but + // reuses the provided iterator to avoid the overhead of constructing a new + // one each time. This speeds it up by 20x! The iterator `it` must already + // be associated with `img`, but it need not be positioned correctly. + auto interppixel = + [](const ImageBuf& img, ImageBuf::ConstIterator& it, float x, + float y, span pixel, ImageBuf::WrapMode wrap) -> bool { + int n = std::min(int(pixel.size()), img.spec().nchannels); + float* localpixel = OIIO_ALLOCA(float, n * 4); + float* p[4] = { localpixel, localpixel + n, localpixel + 2 * n, + localpixel + 3 * n }; + x -= 0.5f; + y -= 0.5f; + int xtexel, ytexel; + float xfrac, yfrac; + xfrac = floorfrac(x, &xtexel); + yfrac = floorfrac(y, &ytexel); + it.rerange(xtexel, xtexel + 2, ytexel, ytexel + 2, 0, 1, wrap); + for (int i = 0; i < 4; ++i, ++it) + for (int c = 0; c < n; ++c) + p[i][c] = it[c]; //NOSONAR + bilerp(p[0], p[1], p[2], p[3], xfrac, yfrac, n, pixel.data()); + return true; + }; + OIIO_ASSERT(src.deep() == dst.deep()); ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) { const ImageSpec& srcspec(src.spec()); const ImageSpec& dstspec(dst.spec()); int nchannels = src.nchannels(); - bool deep = src.deep(); // Local copies of the source image window, converted to float float srcfx = srcspec.full_x; @@ -1109,25 +1166,10 @@ resample_(ImageBuf& dst, const ImageBuf& src, bool interpolate, ROI roi, float s = (x - dstfx + 0.5f) * dstpixelwidth; float src_xf = srcfx + s * srcfw; int src_x = ifloor(src_xf); - if (deep) { - srcpel.pos(src_x, src_y, 0); - int nsamps = srcpel.deep_samples(); - OIIO_DASSERT(nsamps == out.deep_samples()); - if (!nsamps || nsamps != out.deep_samples()) - continue; - for (int c = 0; c < nchannels; ++c) { - if (dstspec.channelformat(c) == TypeDesc::UINT32) - for (int samp = 0; samp < nsamps; ++samp) - out.set_deep_value( - c, samp, srcpel.deep_value_uint(c, samp)); - else - for (int samp = 0; samp < nsamps; ++samp) - out.set_deep_value(c, samp, - srcpel.deep_value(c, samp)); - } - } else if (interpolate) { + if (interpolate) { // Non-deep image, bilinearly interpolate - src.interppixel(src_xf, src_yf, pel, ImageBuf::WrapClamp); + interppixel(src, srcpel, src_xf, src_yf, pel, + ImageBuf::WrapClamp); for (int c = roi.chbegin; c < roi.chend; ++c) out[c] = pel[c]; } else { @@ -1142,6 +1184,266 @@ resample_(ImageBuf& dst, const ImageBuf& src, bool interpolate, ROI roi, return true; } +static bool +resample_deep(ImageBuf& dst, const ImageBuf& src, bool interpolate, ROI roi, + int nthreads) +{ + // If it's deep, figure out the sample allocations first, because + // it's not thread-safe to do that simultaneously with copying the + // values. + const ImageSpec& srcspec(src.spec()); + const ImageSpec& dstspec(dst.spec()); + float srcfx = srcspec.full_x; + float srcfy = srcspec.full_y; + float srcfw = srcspec.full_width; + float srcfh = srcspec.full_height; + float dstpixelwidth = 1.0f / dstspec.full_width; + float dstpixelheight = 1.0f / dstspec.full_height; + ImageBuf::ConstIterator srcpel(src, roi); + ImageBuf::Iterator dstpel(dst, roi); + for (; !dstpel.done(); ++dstpel, ++srcpel) { + float s = (dstpel.x() - dstspec.full_x + 0.5f) * dstpixelwidth; + float t = (dstpel.y() - dstspec.full_y + 0.5f) * dstpixelheight; + int src_y = ifloor(srcfy + t * srcfh); + int src_x = ifloor(srcfx + s * srcfw); + srcpel.pos(src_x, src_y, 0); + dstpel.set_deep_samples(srcpel.deep_samples()); + } + + OIIO_ASSERT(src.deep() == dst.deep()); + ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) { + const ImageSpec& srcspec(src.spec()); + const ImageSpec& dstspec(dst.spec()); + int nchannels = src.nchannels(); + + // Local copies of the source image window, converted to float + float srcfx = srcspec.full_x; + float srcfy = srcspec.full_y; + float srcfw = srcspec.full_width; + float srcfh = srcspec.full_height; + + float dstfx = dstspec.full_x; + float dstfy = dstspec.full_y; + float dstfw = dstspec.full_width; + float dstfh = dstspec.full_height; + float dstpixelwidth = 1.0f / dstfw; + float dstpixelheight = 1.0f / dstfh; + + ImageBuf::Iterator out(dst, roi); + ImageBuf::ConstIterator srcpel(src); + for (int y = roi.ybegin; y < roi.yend; ++y) { + // s,t are NDC space + float t = (y - dstfy + 0.5f) * dstpixelheight; + // src_xf, src_xf are image space float coordinates + float src_yf = srcfy + t * srcfh; + // src_x, src_y are image space integer coordinates of the floor + int src_y = ifloor(src_yf); + for (int x = roi.xbegin; x < roi.xend; ++x, ++out) { + float s = (x - dstfx + 0.5f) * dstpixelwidth; + float src_xf = srcfx + s * srcfw; + int src_x = ifloor(src_xf); + srcpel.pos(src_x, src_y, 0); + int nsamps = srcpel.deep_samples(); + OIIO_DASSERT(nsamps == out.deep_samples()); + if (!nsamps || nsamps != out.deep_samples()) + continue; + for (int c = 0; c < nchannels; ++c) { + if (dstspec.channelformat(c) == TypeDesc::UINT32) + for (int samp = 0; samp < nsamps; ++samp) + out.set_deep_value(c, samp, + srcpel.deep_value_uint(c, samp)); + else + for (int samp = 0; samp < nsamps; ++samp) + out.set_deep_value(c, samp, + srcpel.deep_value(c, samp)); + } + } + } + }); + + return true; +} + + + +#if defined(OIIO_USE_HWY) && OIIO_USE_HWY +template +static bool +resample_hwy(ImageBuf& dst, const ImageBuf& src, bool interpolate, ROI roi, + int nthreads) +{ + using SimdType + = std::conditional_t, double, float>; + using D = hn::ScalableTag; + using Rebind = hn::Rebind; + + ImageBufAlgo::parallel_image(roi, nthreads, [&](ROI roi) { + const ImageSpec& srcspec(src.spec()); + const ImageSpec& dstspec(dst.spec()); + + // Local copies of the source image window, converted to SimdType + float srcfx = srcspec.full_x; + float srcfy = srcspec.full_y; + float srcfw = srcspec.full_width; + float srcfh = srcspec.full_height; + + float dstfx = dstspec.full_x; + float dstfy = dstspec.full_y; + float dstfw = dstspec.full_width; + float dstfh = dstspec.full_height; + float dstpixelwidth = 1.0f / dstfw; + float dstpixelheight = 1.0f / dstfh; + + const size_t src_scanline_bytes = srcspec.scanline_bytes(); + const size_t dst_scanline_bytes = dstspec.scanline_bytes(); + const size_t src_pixel_bytes = srcspec.pixel_bytes(); + const size_t dst_pixel_bytes = dstspec.pixel_bytes(); + + const uint8_t* src_base = (const uint8_t*)src.localpixels(); + uint8_t* dst_base = (uint8_t*)dst.localpixels(); + + D d; + Rebind d_i32; + int N = hn::Lanes(d); + + for (int y = roi.ybegin; y < roi.yend; ++y) { + float t = (y - dstfy + 0.5f) * dstpixelheight; + float src_yf = srcfy + t * srcfh; + // Pixel-center convention: subtract 0.5 before interpolation + src_yf -= 0.5f; + int src_y = ifloor(src_yf); + SimdType fy = (SimdType)(src_yf - src_y); + + // Clamp Y to valid range + int src_y_clamped = clamp(src_y, src.ybegin(), src.yend() - 1); + // Neighbor Y (for bilinear) + int src_y_next_clamped = clamp(src_y + 1, src.ybegin(), + src.yend() - 1); + + // Pre-calculate row pointers + const uint8_t* row0 = src_base + + (src_y_clamped - src.ybegin()) + * src_scanline_bytes; + const uint8_t* row1 = src_base + + (src_y_next_clamped - src.ybegin()) + * src_scanline_bytes; + + uint8_t* dst_row = dst_base + + (y - dst.ybegin()) * dst_scanline_bytes; + + for (int x = roi.xbegin; x < roi.xend; x += N) { + // Handle remaining pixels if less than N + int n = std::min(N, roi.xend - x); + + // Compute src_xf for N pixels + auto idx_i32 = hn::Iota(d_i32, (float)x); + + auto x_simd = hn::ConvertTo(d, idx_i32); + auto s = hn::Mul(hn::Sub(hn::Add(x_simd, + hn::Set(d, (SimdType)0.5f)), + hn::Set(d, (SimdType)dstfx)), + hn::Set(d, (SimdType)dstpixelwidth)); + auto src_xf_vec = hn::MulAdd(s, hn::Set(d, (SimdType)srcfw), + hn::Set(d, (SimdType)srcfx)); + // Pixel-center convention: subtract 0.5 before interpolation + src_xf_vec = hn::Sub(src_xf_vec, hn::Set(d, (SimdType)0.5f)); + + auto src_x_vec = hn::Floor(src_xf_vec); + auto fx = hn::Sub(src_xf_vec, src_x_vec); + auto ix = hn::ConvertTo(d_i32, src_x_vec); + + // Clamp X + auto min_x = hn::Set(d_i32, src.xbegin()); + auto max_x = hn::Set(d_i32, src.xend() - 1); + auto ix0 = hn::Min(hn::Max(ix, min_x), max_x); + auto ix1 + = hn::Min(hn::Max(hn::Add(ix, hn::Set(d_i32, 1)), min_x), + max_x); + + // Adjust to 0-based offset from buffer start + auto x_offset = hn::Sub(ix0, min_x); + auto x1_offset = hn::Sub(ix1, min_x); + + // Loop over channels + for (int c = roi.chbegin; c < roi.chend; ++c) { + // Manual gather loop for now to be safe with types and offsets + SimdType v00_arr[16], v01_arr[16], v10_arr[16], v11_arr[16]; + int32_t x0_arr[16], x1_arr[16]; + hn::Store(x_offset, d_i32, x0_arr); + hn::Store(x1_offset, d_i32, x1_arr); + + for (int i = 0; i < n; ++i) { + size_t off0 = (size_t)x0_arr[i] * src_pixel_bytes + + (size_t)c * sizeof(SRCTYPE); + size_t off1 = (size_t)x1_arr[i] * src_pixel_bytes + + (size_t)c * sizeof(SRCTYPE); + + auto load_val = [](const uint8_t* ptr) -> SimdType { + return (SimdType)(*(const SRCTYPE*)ptr); + }; + + v00_arr[i] = load_val(row0 + off0); + v01_arr[i] = load_val(row0 + off1); + v10_arr[i] = load_val(row1 + off0); + v11_arr[i] = load_val(row1 + off1); + } + + auto val00 = hn::Load(d, v00_arr); + auto val01 = hn::Load(d, v01_arr); + auto val10 = hn::Load(d, v10_arr); + auto val11 = hn::Load(d, v11_arr); + + // Bilinear Interpolation + auto one = hn::Set(d, (SimdType)1.0f); + auto w00 = hn::Mul(hn::Sub(one, fx), + hn::Sub(one, hn::Set(d, fy))); + auto w01 = hn::Mul(fx, hn::Sub(one, hn::Set(d, fy))); + auto w10 = hn::Mul(hn::Sub(one, fx), hn::Set(d, fy)); + auto w11 = hn::Mul(fx, hn::Set(d, fy)); + + // Use FMA (Fused Multiply-Add) for better performance + auto res = hn::Mul(val00, w00); + res = hn::MulAdd(val01, w01, + res); // res = res + val01 * w01 + res = hn::MulAdd(val10, w10, + res); // res = res + val10 * w10 + res = hn::MulAdd(val11, w11, + res); // res = res + val11 * w11 + + // Store + SimdType res_arr[16]; + hn::Store(res, d, res_arr); + for (int i = 0; i < n; ++i) { + DSTTYPE* dptr + = (DSTTYPE*)(dst_row + + (size_t)(x - roi.xbegin + i) + * dst_pixel_bytes + + (size_t)c * sizeof(DSTTYPE)); + *dptr = (DSTTYPE)res_arr[i]; + } + } + } + } + }); + return true; +} +#endif // defined(OIIO_USE_HWY) && OIIO_USE_HWY + +template +static bool +resample_(ImageBuf& dst, const ImageBuf& src, bool interpolate, ROI roi, + int nthreads) +{ +#if defined(OIIO_USE_HWY) && OIIO_USE_HWY + if (OIIO::pvt::enable_hwy && dst.localpixels() && src.localpixels()) + return resample_hwy(dst, src, interpolate, roi, + nthreads); +#endif + + return resample_scalar(dst, src, interpolate, roi, + nthreads); +} + bool @@ -1155,27 +1457,7 @@ ImageBufAlgo::resample(ImageBuf& dst, const ImageBuf& src, bool interpolate, return false; if (dst.deep()) { - // If it's deep, figure out the sample allocations first, because - // it's not thread-safe to do that simultaneously with copying the - // values. - const ImageSpec& srcspec(src.spec()); - const ImageSpec& dstspec(dst.spec()); - float srcfx = srcspec.full_x; - float srcfy = srcspec.full_y; - float srcfw = srcspec.full_width; - float srcfh = srcspec.full_height; - float dstpixelwidth = 1.0f / dstspec.full_width; - float dstpixelheight = 1.0f / dstspec.full_height; - ImageBuf::ConstIterator srcpel(src, roi); - ImageBuf::Iterator dstpel(dst, roi); - for (; !dstpel.done(); ++dstpel, ++srcpel) { - float s = (dstpel.x() - dstspec.full_x + 0.5f) * dstpixelwidth; - float t = (dstpel.y() - dstspec.full_y + 0.5f) * dstpixelheight; - int src_y = ifloor(srcfy + t * srcfh); - int src_x = ifloor(srcfx + s * srcfw); - srcpel.pos(src_x, src_y, 0); - dstpel.set_deep_samples(srcpel.deep_samples()); - } + return resample_deep(dst, src, interpolate, roi, nthreads); } bool ok; diff --git a/src/libOpenImageIO/imageinout_test.cpp b/src/libOpenImageIO/imageinout_test.cpp index bcf4b072b8..98cf25cc49 100644 --- a/src/libOpenImageIO/imageinout_test.cpp +++ b/src/libOpenImageIO/imageinout_test.cpp @@ -101,8 +101,8 @@ make_test_image(string_view formatname) static bool checked_write(ImageOutput* out, string_view filename, const ImageSpec& spec, - TypeDesc type, const void* data, bool do_asserts = true, - std::string* errmsg = nullptr, + TypeDesc type, image_span data, + bool do_asserts = true, std::string* errmsg = nullptr, Filesystem::IOProxy* ioproxy = nullptr) { if (errmsg) @@ -117,7 +117,7 @@ checked_write(ImageOutput* out, string_view filename, const ImageSpec& spec, if (errmsg) *errmsg = OIIO::geterror(); else - std::cout << " " << OIIO::geterror() << "\n"; + print(" {}\n", OIIO::geterror()); return false; } @@ -131,19 +131,27 @@ checked_write(ImageOutput* out, string_view filename, const ImageSpec& spec, static bool checked_read(ImageInput* in, string_view filename, - std::vector& data, bool already_opened = false, - bool do_asserts = true, std::string* errmsg = nullptr) + std::vector& data, TypeDesc datatype = TypeFloat, + bool already_opened = false, bool do_asserts = true, + std::string* errmsg = nullptr) { if (errmsg) *errmsg = ""; + std::unique_ptr in_local; + if (!in) { + in_local = ImageInput::create(filename); + in = in_local.get(); + already_opened = false; + } + OIIO_CHECK_ASSERT(in && "Failed to create input"); if (!already_opened) { ImageSpec spec; CHECKED(in, open(filename, spec)); } data.resize(in->spec().image_pixels() * in->spec().nchannels - * sizeof(float)); + * datatype.size()); CHECKED(in, - read_image(0, 0, 0, in->spec().nchannels, TypeFloat, data.data())); + read_image(0, 0, 0, in->spec().nchannels, datatype, data.data())); CHECKED(in, close()); return true; } @@ -164,7 +172,8 @@ test_write_proxy(string_view formatname, string_view extension, // Use ImageOutput.write_image interface to write to outproxy Filesystem::IOVecOutput outproxy; ok = checked_write(nullptr, disk_filename, buf.spec(), buf.spec().format, - buf.localpixels(), true, nullptr, &outproxy); + buf.localpixels_as_writable_byte_image_span(), true, + nullptr, &outproxy); // Use ImageBuf.write interface to write to outproxybuf Filesystem::IOVecOutput outproxybuf; @@ -281,7 +290,7 @@ test_read_proxy(string_view formatname, string_view extension, OIIO_CHECK_ASSERT(in && "Failed to open input with proxy"); if (in) { std::vector readpixels; - ok &= checked_read(in.get(), memname, readpixels, true); + ok &= checked_read(in.get(), memname, readpixels, TypeFloat, true); OIIO_ASSERT(readpixels.size() == nvalues * sizeof(float)); ok &= test_pixel_match({ (const float*)readpixels.data(), nvalues }, { (const float*)buf.localpixels(), nvalues }, @@ -331,7 +340,8 @@ test_write_unwritable(string_view extension, const ImageBuf& buf) if (badout) { std::string errmsg; ok = checked_write(badout.get(), bad_filename, buf.spec(), - buf.spec().format, buf.localpixels(), + buf.spec().format, + buf.localpixels_as_byte_image_span(), /*do_asserts=*/false, &errmsg); if (!ok) std::cout << term.ansi("green", "OK") << " (" @@ -391,7 +401,7 @@ test_all_formats() std::cout << " Writing " << filename << " ... "; ok = checked_write(out.get(), filename, buf.spec(), buf.spec().format, - orig_pixels); + buf.localpixels_as_writable_byte_image_span()); if (ok) std::cout << term.ansi("green", "OK\n"); @@ -531,6 +541,68 @@ test_read_tricky_sizes() +void +benchmark_tile_sizes(string_view extension, TypeDesc datatype, + int tilestart = 4) +{ + const int test_res = 4096; + std::vector tile_sizes; + for (int ts = tilestart; ts <= test_res / 2; ts *= 2) + tile_sizes.push_back(ts); + ImageSpec test_image_spec(4096, 2048, 4, datatype); + ImageBuf buf(test_image_spec); + static float colors[4][4] = { { 0.1f, 0.1f, 0.1f, 1.0f }, + { 1.0f, 0.0f, 0.0f, 1.0f }, + { 0.0f, 1.0f, 0.0f, 1.0f }, + { 0.0f, 0.0f, 1.0f, 1.0f } }; + // ImageBufAlgo::fill(buf, make_cspan(colors[1], 4)); + ImageBufAlgo::fill(buf, colors[0], colors[1], colors[2], colors[3]); + buf.write(Strutil::format("test.{}", extension)); + + Benchmarker bench; + bench.units(Benchmarker::Unit::ms); + bench.iterations(1); + bench.trials(5); + print("\nBenchmarking write/read for {} under different tile sizes\n", + extension); + + // Write a scanline file + auto scanline_filename = Strutil::format("test_scanline.{}", extension); + bench(Strutil::format(" write {} scanline ", extension), [&]() { + checked_write(nullptr, scanline_filename, test_image_spec, datatype, + buf.localpixels_as_byte_image_span()); + }); + + // Write tiled files of different sizes + for (auto ts : tile_sizes) { + test_image_spec.tile_width = ts; + test_image_spec.tile_height = ts; + test_image_spec.tile_depth = ts ? 1 : 0; + auto filename = Strutil::format("test_tile_{:04}.{}", ts, extension); + bench(Strutil::format(" write {} tile {}", extension, ts), [&]() { + checked_write(nullptr, filename, test_image_spec, datatype, + buf.localpixels_as_byte_image_span()); + }); + } + + // read the scanline file (and delete it when we're done) + std::vector readbuffer(test_image_spec.image_bytes()); + bench(Strutil::format(" read {} scanline ", extension), [&]() { + checked_read(nullptr, scanline_filename, readbuffer, datatype); + }); + Filesystem::remove(scanline_filename); + + // read the tiled files of different sizes (and delete when done) + for (auto ts : tile_sizes) { + auto filename = Strutil::format("test_tile_{:04}.{}", ts, extension); + bench(Strutil::format(" read {} tile {}", extension, ts), + [&]() { checked_read(nullptr, filename, readbuffer, datatype); }); + Filesystem::remove(filename); + } +} + + + int main(int argc, char* argv[]) { @@ -549,6 +621,8 @@ main(int argc, char* argv[]) test_all_formats(); test_read_tricky_sizes(); + benchmark_tile_sizes("exr", TypeHalf, 4); + benchmark_tile_sizes("tif", TypeUInt16, 16); return unit_test_failures; } diff --git a/src/libOpenImageIO/imageinput.cpp b/src/libOpenImageIO/imageinput.cpp index 03af1297c5..3ff8f1a255 100644 --- a/src/libOpenImageIO/imageinput.cpp +++ b/src/libOpenImageIO/imageinput.cpp @@ -211,6 +211,40 @@ ImageInput::spec_dimensions(int subimage, int miplevel) +// Utility: Make sure the provided data span is the right size for the +// image described by spec and datatype. If they don't match, issue an +// error and return false. +static bool +check_span_size(ImageInput* in, string_view caller, const ImageSpec& spec, + TypeDesc datatype, imagesize_t npixels, int chbegin, int chend, + const image_span& data) +{ + // One of two things must be correct: Either format is Unknown and the + // total byte size needs to match the "native" size, or the format is + // concrete and the number of value must match (it's ok if the size + // doesn't match, since a data type conversion will occur). + if (datatype.is_unknown()) { // Unknown assumes native chan types + size_t sz = npixels * spec.pixel_bytes(chbegin, chend, true); + if (sz != data.size_bytes()) { + in->errorfmt( + "{}: image_span size is incorrect ({} bytes vs {} needed)", + caller, data.size_bytes(), sz); + return false; + } + } else { // single concrete type + size_t nvals = npixels * size_t(chend - chbegin); + if (nvals != data.nvalues()) { + in->errorfmt( + "{}: image_span size is incorrect ({} values vs {} needed)", + caller, data.nvalues(), nvals); + return false; + } + } + return true; +} + + + bool ImageInput::read_scanline(int y, int z, TypeDesc format, void* data, stride_t xstride) @@ -300,16 +334,10 @@ ImageInput::read_scanlines(int subimage, int miplevel, int ybegin, int yend, chend); return false; } - size_t isize = (format == TypeUnknown - ? spec.pixel_bytes(chbegin, chend, true /*native*/) - : format.size() * (chend - chbegin)) - * size_t(spec.width); - if (isize != data.size_bytes()) { - errorfmt( - "read_scanlines: Buffer size is incorrect ({} bytes vs {} needed)", - isize, data.size_bytes()); + if (!check_span_size(this, "read_scanlines", m_spec, format, + m_spec.width * size_t(yend - ybegin), chbegin, chend, + data)) return false; - } // Default implementation (for now): call the old pointer+stride return read_scanlines(subimage, miplevel, ybegin, yend, 0, chbegin, chend, @@ -656,16 +684,11 @@ ImageInput::read_tiles(int subimage, int miplevel, int xbegin, int xend, errorfmt("read_tiles: invalid channel range [{},{})", chbegin, chend); return false; } - size_t isize = (format == TypeUnknown - ? spec.pixel_bytes(chbegin, chend, true /*native*/) - : format.size() * (chend - chbegin)) - * size_t(xend - xbegin) * size_t(yend - ybegin) - * size_t(zend - zbegin); - if (isize != data.size_bytes()) { - errorfmt("read_tiles: Buffer size is incorrect ({} bytes vs {} needed)", - isize, data.size_bytes()); + if (!check_span_size(this, "read_tiles", m_spec, format, + size_t(xend - xbegin) * size_t(yend - ybegin) + * size_t(zend - zbegin), + chbegin, chend, data)) return false; - } // Default implementation (for now): call the old pointer+stride return read_tiles(subimage, miplevel, ybegin, yend, xbegin, xend, zbegin, @@ -1164,24 +1187,6 @@ bool ImageInput::read_image(int subimage, int miplevel, int chbegin, int chend, TypeDesc format, const image_span& data) { -#if 0 - ImageSpec spec = spec_dimensions(subimage, miplevel); - if (chend < 0 || chend > spec.nchannels) - chend = spec.nchannels; - size_t isize = (format == TypeUnknown - ? spec.pixel_bytes(chbegin, chend, true /*native*/) - : format.size() * (chend - chbegin)) - * spec.image_pixels(); - if (isize != data.size_bytes()) { - errorfmt("read_image: Buffer size is incorrect ({} bytes vs {} needed)", - sz, data.size_bytes()); - return false; - } - - // Default implementation (for now): call the old pointer+stride - return read_image(subimage, miplevel, chbegin, chend, format, data.data(), - data.xstride(), data.ystride(), data.zstride()); -#else OIIO::pvt::LoggedTimer logtime("II::read_image"); ImageSpec spec; int rps = 0; @@ -1210,16 +1215,9 @@ ImageInput::read_image(int subimage, int miplevel, int chbegin, int chend, errorfmt("read_image: invalid channel range [{},{})", chbegin, chend); return false; } - int nchans = chend - chbegin; - bool native = (format == TypeUnknown); - size_t pixel_bytes = native ? spec.pixel_bytes(chbegin, chend, native) - : (format.size() * nchans); - size_t isize = pixel_bytes * spec.image_pixels(); - if (isize != data.size_bytes()) { - errorfmt("read_image: Buffer size is incorrect ({} bytes vs {} needed)", - isize, data.size_bytes()); + if (!check_span_size(this, "read_image", m_spec, format, + spec.image_pixels(), chbegin, chend, data)) return false; - } bool ok = true; if (spec.tile_width) { // Tiled image -- rely on read_tiles @@ -1259,7 +1257,6 @@ ImageInput::read_image(int subimage, int miplevel, int chbegin, int chend, } } return ok; -#endif } diff --git a/src/libOpenImageIO/imageio.cpp b/src/libOpenImageIO/imageio.cpp index 909f8529d4..f8b5afa7af 100644 --- a/src/libOpenImageIO/imageio.cpp +++ b/src/libOpenImageIO/imageio.cpp @@ -53,6 +53,7 @@ int png_linear_premult(0); int tiff_half(0); int tiff_multithread(1); int dds_bc5normal(0); +int enable_hwy(1); // Enable Google Highway SIMD optimizations by default int limit_channels(1024); int limit_imagesize_MB(std::min(32 * 1024, int(Sysutil::physical_memory() >> 20))); @@ -220,9 +221,7 @@ oiio_build_compiler() using Strutil::fmt::format; std::string comp; -#if OIIO_INTEL_CLASSIC_COMPILER_VERSION - comp = format("Intel icc {}", OIIO_INTEL_CLASSIC_COMPILER_VERSION); -#elif OIIO_INTEL_LLVM_COMPILER +#if OIIO_INTEL_LLVM_COMPILER comp = format("Intel icx {}.{}", __clang_major__, __clang_minor__); #elif OIIO_APPLE_CLANG_VERSION comp = format("Apple clang {}.{}", __clang_major__, __clang_minor__); @@ -257,14 +256,15 @@ oiio_build_platform() platform = "UnknownOS"; #endif platform += "/"; -#if defined(__x86_64__) +#if defined(__x86_64__) || defined(_M_AMD64) platform += "x86_64"; -#elif defined(__i386__) +#elif defined(__i386__) || defined(_M_IX86) platform += "i386"; -#elif defined(_M_ARM64) || defined(__aarch64__) || defined(__aarch64) +#elif defined(_M_ARM64) || defined(__aarch64__) || defined(__aarch64) \ + || defined(__ARM_ARCH) platform += "ARM"; #else - platform = "unknown arch?"; + platform += "unknown arch?"; #endif return platform; } @@ -406,6 +406,10 @@ attribute(string_view name, TypeDesc type, const void* val) dds_bc5normal = *(const int*)val; return true; } + if (name == "enable_hwy" && type == TypeInt) { + enable_hwy = *(const int*)val; + return true; + } if (name == "limits:channels" && type == TypeInt) { limit_channels = *(const int*)val; return true; @@ -612,6 +616,10 @@ getattribute(string_view name, TypeDesc type, void* val) *(int*)val = dds_bc5normal; return true; } + if (name == "enable_hwy" && type == TypeInt) { + *(int*)val = enable_hwy; + return true; + } if (name == "oiio:print_uncaught_errors" && type == TypeInt) { *(int*)val = oiio_print_uncaught_errors; return true; diff --git a/src/libOpenImageIO/imageoutput.cpp b/src/libOpenImageIO/imageoutput.cpp index 1b9edaede5..216c2a18f9 100644 --- a/src/libOpenImageIO/imageoutput.cpp +++ b/src/libOpenImageIO/imageoutput.cpp @@ -102,6 +102,40 @@ ImageOutput::~ImageOutput() +// Utility: Make sure the provided data span is the right size for the +// image described by spec and datatype. If they don't match, issue an +// error and return false. +static bool +check_span_size(ImageOutput* out, string_view caller, const ImageSpec& spec, + TypeDesc datatype, imagesize_t npixels, + const image_span& data) +{ + // One of two things must be correct: Either format is Unknown and the + // total byte size needs to match the "native" size, or the format is + // concrete and the number of value must match (it's ok if the size + // doesn't match, since a data type conversion will occur). + if (datatype.is_unknown()) { // Unknown assumes native chan types + size_t sz = npixels * spec.pixel_bytes(true); + if (sz != data.size_bytes()) { + out->errorfmt( + "{}: image_span size is incorrect ({} bytes vs {} needed)", + caller, data.size_bytes(), sz); + return false; + } + } else { // single concrete type + size_t nvals = npixels * size_t(spec.nchannels); + if (nvals != data.nvalues()) { + out->errorfmt( + "{}: image_span size is incorrect ({} values vs {} needed)", + caller, data.nvalues(), nvals); + return false; + } + } + return true; +} + + + bool ImageOutput::write_scanline(int /*y*/, int /*z*/, TypeDesc /*format*/, const void* /*data*/, stride_t /*xstride*/) @@ -120,13 +154,9 @@ ImageOutput::write_scanline(int y, TypeDesc format, errorfmt("write_scanlines: Invalid scanline index {}", y); return false; } - size_t sz = m_spec.scanline_bytes(format); - if (sz != data.size_bytes()) { - errorfmt( - "write_scanline: Buffer size is incorrect ({} bytes vs {} needed)", - sz, data.size_bytes()); + if (!check_span_size(this, "write_scanline", m_spec, format, m_spec.width, + data)) return false; - } // Default implementation (for now): call the old pointer+stride return write_scanline(y, 0, format, data.data(), data.xstride()); @@ -164,13 +194,9 @@ ImageOutput::write_scanlines(int ybegin, int yend, TypeDesc format, errorfmt("write_scanlines: Invalid scanline range {}-{}", ybegin, yend); return false; } - size_t sz = m_spec.scanline_bytes(format) * size_t(yend - ybegin); - if (sz != data.size_bytes()) { - errorfmt( - "write_scanlines: Buffer size is incorrect ({} bytes vs {} needed)", - sz, data.size_bytes()); + if (!check_span_size(this, "write_scanlines", m_spec, format, + m_spec.width * size_t(yend - ybegin), data)) return false; - } // Default implementation (for now): call the old pointer+stride return write_scanlines(ybegin, yend, 0, format, data.data(), data.xstride(), @@ -194,15 +220,9 @@ bool ImageOutput::write_tile(int x, int y, int z, TypeDesc format, const image_span& data) { - size_t sz = format == TypeUnknown - ? m_spec.pixel_bytes(true /*native*/) - : m_spec.tile_pixels() * size_t(m_spec.nchannels) - * format.size(); - if (sz != data.size_bytes()) { - errorfmt("write_tile: Buffer size is incorrect ({} bytes vs {} needed)", - sz, data.size_bytes()); + if (!check_span_size(this, "write_tile", m_spec, format, + m_spec.tile_pixels(), data)) return false; - } // Default implementation (for now): call the old pointer+stride return write_tile(x, y, z, format, data.data(), data.xstride(), @@ -691,12 +711,9 @@ bool ImageOutput::write_image(TypeDesc format, const image_span& data) { - size_t sz = m_spec.image_bytes(/*native=*/format == TypeUnknown); - if (sz != data.size_bytes()) { - errorfmt("write_image: Buffer size is incorrect ({} bytes vs {} needed)", - sz, data.size_bytes()); + if (!check_span_size(this, "write_image", m_spec, format, + m_spec.image_pixels(), data)) return false; - } // Default implementation (for now): call the old pointer+stride return write_image(format, data.data(), data.xstride(), data.ystride(), diff --git a/src/libOpenImageIO/iptc.cpp b/src/libOpenImageIO/iptc.cpp index b8ee573059..eed82eeb45 100644 --- a/src/libOpenImageIO/iptc.cpp +++ b/src/libOpenImageIO/iptc.cpp @@ -181,17 +181,21 @@ decode_iptc_iim(const void* iptc, int length, ImageSpec& spec) static void encode_iptc_iim_one_tag(int tag, string_view data, std::vector& iptc) { - OIIO_DASSERT(data != nullptr); + if (data.size() == 0) + return; + data = data.substr(0, 0xffff); // Truncate to prevent 16 bit overflow + size_t tagsize = data.size(); iptc.push_back((char)0x1c); iptc.push_back((char)0x02); iptc.push_back((char)tag); - if (data.size()) { - int tagsize = std::min(int(data.size()), - 0xffff - 1); // Prevent 16 bit overflow - iptc.push_back((char)(tagsize >> 8)); - iptc.push_back((char)(tagsize & 0xff)); - iptc.insert(iptc.end(), data.data(), data.data() + tagsize); - } + iptc.push_back((char)(tagsize >> 8)); + iptc.push_back((char)(tagsize & 0xff)); + OIIO_PRAGMA_WARNING_PUSH + OIIO_GCC_ONLY_PRAGMA(GCC diagnostic ignored "-Wstringop-overflow") + // Suppress what I'm sure is a false positive warning when + // _GLIBCXX_ASSERTIONS is enabled. + iptc.insert(iptc.end(), data.begin(), data.end()); + OIIO_PRAGMA_WARNING_POP } @@ -208,7 +212,7 @@ encode_iptc_iim(const ImageSpec& spec, std::vector& iptc) std::string allvals = p->get_string(0); std::vector tokens; Strutil::split(allvals, tokens, ";"); - for (auto& token : tokens) { + for (auto token : tokens) { token = Strutil::strip(token); if (token.size()) { if (iimtag[i].maxlen && iimtag[i].maxlen < token.size()) diff --git a/src/libOpenImageIO/maketexture.cpp b/src/libOpenImageIO/maketexture.cpp index ca38206523..533cf7dc61 100644 --- a/src/libOpenImageIO/maketexture.cpp +++ b/src/libOpenImageIO/maketexture.cpp @@ -510,12 +510,11 @@ bump_to_bumpslopes(ImageBuf& dst, const ImageBuf& src, return false; } is_height = false; - } else if (Strutil::iequals( - bumpformat, - "auto")) { // guess input bump format by analyzing channel count and component + } else if (Strutil::iequals(bumpformat, "auto")) { + // guess input bump format by analyzing channel count and component if (src.spec().nchannels > 2 - && !ImageBufAlgo::isMonochrome(src)) // maybe it's a normal map? - is_height = false; + && !ImageBufAlgo::isMonochrome(src, 0.0f, ROI(), nthreads)) + is_height = false; // maybe it's a normal map? else is_height = true; } else { @@ -705,6 +704,8 @@ write_mipmap(ImageBufAlgo::MakeTextureMode mode, std::shared_ptr& img, ImageSpec outspec = outspec_template; outspec.set_format(outputdatatype); + int nthreads = configspec.get_int_attribute("maketx:threads"); + // Going from float to half is prone to generating Inf values if we had // any floats that were out of the range that half can represent. Nobody // wants Inf in textures; better to clamp. @@ -783,7 +784,8 @@ write_mipmap(ImageBufAlgo::MakeTextureMode mode, std::shared_ptr& img, if (clamp_half) { std::shared_ptr tmp(new ImageBuf); - ImageBufAlgo::clamp(*tmp, *img, -HALF_MAX, HALF_MAX, true); + ImageBufAlgo::clamp(*tmp, *img, -HALF_MAX, HALF_MAX, true, ROI(), + nthreads); std::swap(tmp, img); } if (!img->write(out)) { @@ -831,7 +833,8 @@ write_mipmap(ImageBufAlgo::MakeTextureMode mode, std::shared_ptr& img, std::shared_ptr t(new ImageBuf(smallspec)); ImageBufAlgo::channels(*t, *small, outspec.nchannels, cspan(), cspan(), - cspan(), true); + cspan(), true, + nthreads); std::swap(t, small); } smallspec.tile_width = outspec.tile_width; @@ -863,7 +866,7 @@ write_mipmap(ImageBufAlgo::MakeTextureMode mode, std::shared_ptr& img, // and pixel windows match. Don't worry, the texture // engine doesn't care what the upper MIP levels have // for the window sizes, it uses level 0 to determine - // the relatinship between texture 0-1 space (display + // the relationship between texture 0-1 space (display // window) and the pixels. smallspec.x = 0; smallspec.y = 0; @@ -875,12 +878,11 @@ write_mipmap(ImageBufAlgo::MakeTextureMode mode, std::shared_ptr& img, if (filtername == "box" && !orig_was_overscan && sharpen <= 0.0f) { - ImageBufAlgo::parallel_image(get_roi(small->spec()), - std::bind(resize_block, - std::ref(*small), - std::cref(*img), _1, - envlatlmode, - allow_shift)); + ImageBufAlgo::parallel_image( + get_roi(small->spec()), paropt(nthreads), [&](ROI roi) { + resize_block(*small, *img, roi, envlatlmode, + allow_shift); + }); } else { Filter2D* filter = setup_filter(small->spec(), img->spec(), filtername); @@ -901,38 +903,44 @@ write_mipmap(ImageBufAlgo::MakeTextureMode mode, std::shared_ptr& img, OIIO::print(outstream, "\n"); } if (do_highlight_compensation) - ImageBufAlgo::rangecompress(*img, *img); + ImageBufAlgo::rangecompress(*img, *img, false, ROI(), + nthreads); if (sharpen > 0.0f && sharpen_first) { std::shared_ptr sharp(new ImageBuf); bool uok = ImageBufAlgo::unsharp_mask(*sharp, *img, - sharpenfilt, 3.0, - sharpen, 0.0f); + sharpenfilt, 3.0f, + sharpen, 0.0f, + ROI(), nthreads); if (!uok) errorfmt("{}", sharp->geterror()); std::swap(img, sharp); } ImageBufAlgo::resize(*small, *img, - { make_pv("filterptr", filter) }); + { make_pv("filterptr", filter) }, + ROI(), nthreads); if (sharpen > 0.0f && !sharpen_first) { std::shared_ptr sharp(new ImageBuf); bool uok = ImageBufAlgo::unsharp_mask(*sharp, *small, - sharpenfilt, 3.0, - sharpen, 0.0f); + sharpenfilt, 3.0f, + sharpen, 0.0f, + ROI(), nthreads); if (!uok) errorfmt("{}", sharp->geterror()); std::swap(small, sharp); } if (do_highlight_compensation) { - ImageBufAlgo::rangeexpand(*small, *small); + ImageBufAlgo::rangeexpand(*small, *small, false, ROI(), + nthreads); ImageBufAlgo::clamp(*small, *small, 0.0f, std::numeric_limits::max(), - true); + true, ROI(), nthreads); } Filter2D::destroy(filter); } } if (clamp_half) - ImageBufAlgo::clamp(*small, *small, -HALF_MAX, HALF_MAX, true); + ImageBufAlgo::clamp(*small, *small, -HALF_MAX, HALF_MAX, true, + ROI(), nthreads); double this_miptime = miptimer(); stat_miptime += this_miptime; @@ -1093,6 +1101,8 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, if (!configspec.tile_depth) configspec.tile_depth = 1; + int nthreads = configspec.get_int_attribute("maketx:threads"); + bool ignore_unassoc = configspec.get_int_attribute("maketx:ignore_unassoc"); ImageSpec inconfig; if (ignore_unassoc) @@ -1257,8 +1267,7 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, bool ok = true; OIIO_DISPATCH_COMMON_TYPES(ok, "lightprobe_to_envlatl", lightprobe_to_envlatl, src->spec().format, - *latlong, *src, true); - // lightprobe_to_envlatl(*latlong, *src, true); + *latlong, *src, true, ROI(), nthreads); // Carry on with the lat-long environment map from here on out mode = ImageBufAlgo::MakeTxEnvLatl; src = latlong; @@ -1269,7 +1278,8 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, if (Strutil::iequals(configspec.get_string_attribute("maketx:bumprange", "auto"), "auto")) - src_pixel_stats = ImageBufAlgo::computePixelStats(*src); + src_pixel_stats = ImageBufAlgo::computePixelStats(*src, ROI(), + nthreads); ImageSpec newspec = src->spec(); newspec.tile_width = newspec.tile_height = 0; @@ -1286,7 +1296,8 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, bool ok; OIIO_DISPATCH_COMMON_TYPES(ok, "bump_to_bumpslopes", bump_to_bumpslopes, src->spec().format, *bumpslopes, *src, - configspec, src_pixel_stats, outstream); + configspec, src_pixel_stats, outstream, + ROI(), nthreads); // bump_to_bumpslopes(*bumpslopes, *src); mode = ImageBufAlgo::MakeTxTexture; src = bumpslopes; @@ -1330,7 +1341,8 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, std::vector hist; for (int i = 0; i < channels; i++) { - hist = ImageBufAlgo::histogram(*src, i, bins, 0.0f, 1.0f); + hist = ImageBufAlgo::histogram(*src, i, bins, 0.0f, 1.0f, false, + ROI(), nthreads); // Turn the histogram into a non-normalized CDF for (uint64_t j = 1; j < bins; j++) { @@ -1389,7 +1401,7 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, bool compute_stats = (constant_color_detect || opaque_detect || compute_average_color || monochrome_detect); if (compute_stats) { - pixel_stats = ImageBufAlgo::computePixelStats(*src); + pixel_stats = ImageBufAlgo::computePixelStats(*src, ROI(), nthreads); } double stat_pixelstatstime = alltime.lap(); STATUS("pixelstats", stat_pixelstatstime); @@ -1420,7 +1432,7 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, newspec.full_height = newspec.height; newspec.full_depth = newspec.depth; src->reset(newspec); - ImageBufAlgo::fill(*src, constantColor); + ImageBufAlgo::fill(*src, constantColor, ROI(), nthreads); if (verbose) { outstream << " Constant color image detected. "; outstream << "Creating " << newspec.width << "x" @@ -1441,7 +1453,7 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, std::shared_ptr newsrc(new ImageBuf(src->spec())); ImageBufAlgo::channels(*newsrc, *src, src->nchannels() - 1, cspan(), cspan(), - cspan(), true); + cspan(), true, nthreads); std::swap(src, newsrc); // N.B. the old src will delete } @@ -1455,14 +1467,14 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, && src->spec().alpha_channel < 0 && pixel_stats.avg[0] == pixel_stats.avg[1] && pixel_stats.avg[0] == pixel_stats.avg[2] - && ImageBufAlgo::isMonochrome(*src)) { + && ImageBufAlgo::isMonochrome(*src, 0.0f, ROI(), nthreads)) { if (verbose) OIIO::print( outstream, " Monochrome image detected. Converting to single channel texture.\n"); std::shared_ptr newsrc(new ImageBuf(src->spec())); ImageBufAlgo::channels(*newsrc, *src, 1, cspan(), cspan(), - cspan(), true); + cspan(), true, nthreads); newsrc->specmod().default_channel_names(); std::swap(src, newsrc); } @@ -1475,7 +1487,8 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, << std::endl; std::shared_ptr newsrc(new ImageBuf(src->spec())); ImageBufAlgo::channels(*newsrc, *src, nchannels, cspan(), - cspan(), cspan(), true); + cspan(), cspan(), true, + nthreads); std::swap(src, newsrc); } @@ -1663,7 +1676,8 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, && (srcspec.format.basetype == TypeDesc::FLOAT || srcspec.format.basetype == TypeDesc::HALF || srcspec.format.basetype == TypeDesc::DOUBLE) - && !ImageBufAlgo::fixNonFinite(*src, *src, fixmode, &pixelsFixed)) { + && !ImageBufAlgo::fixNonFinite(*src, *src, fixmode, &pixelsFixed, ROI(), + nthreads)) { errorfmt("Error fixing nans/infs."); return false; } @@ -1677,7 +1691,7 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, || srcspec.format.basetype == TypeDesc::HALF || srcspec.format.basetype == TypeDesc::DOUBLE)) { int found_nonfinite = 0; - ImageBufAlgo::parallel_image(get_roi(srcspec), + ImageBufAlgo::parallel_image(get_roi(srcspec), nthreads, std::bind(check_nan_block, std::ref(*src), _1, std::ref(found_nonfinite))); if (found_nonfinite) { @@ -1736,7 +1750,7 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, outstream << " Unpremulting image..." << std::endl; if (!ImageBufAlgo::colorconvert(*ccSrc, *src, processor.get(), - unpremult)) { + unpremult, ROI(), nthreads)) { errorfmt("Error applying color conversion to image."); return false; } @@ -1838,10 +1852,12 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, toplevel.reset(new ImageBuf(dstspec)); if ((resize_filter == "box" || resize_filter == "triangle") && !orig_was_overscan) { - ImageBufAlgo::parallel_image( - get_roi(dstspec), - std::bind(resize_block, std::ref(*toplevel), std::cref(*src), - _1, envlatlmode, allow_shift != 0)); + ImageBufAlgo::parallel_image(get_roi(dstspec), nthreads, + [&](ROI roi) { + resize_block(*toplevel, *src, roi, + envlatlmode, + allow_shift != 0); + }); } else { Filter2D* filter = setup_filter(toplevel->spec(), src->spec(), resize_filter); @@ -1850,7 +1866,8 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, return false; } ImageBufAlgo::resize(*toplevel, *src, - { make_pv("filterptr", filter) }); + { make_pv("filterptr", filter) }, ROI(), + nthreads); Filter2D::destroy(filter); } } @@ -1906,12 +1923,11 @@ make_texture_impl(ImageBufAlgo::MakeTextureMode mode, const ImageBuf* input, addlHashData << "keepaspect=1 "; const int sha1_blocksize = 256; - std::string hash_digest - = configspec.get_int_attribute("maketx:hash", 1) - ? ImageBufAlgo::computePixelHashSHA1(*toplevel, - addlHashData.str(), - ROI::All(), sha1_blocksize) - : ""; + std::string hash_digest = configspec.get_int_attribute("maketx:hash", 1) + ? ImageBufAlgo::computePixelHashSHA1( + *toplevel, addlHashData.str(), ROI::All(), + sha1_blocksize, nthreads) + : ""; if (hash_digest.length()) { if (out->supports("arbitrary_metadata")) { dstspec.attribute("oiio:SHA-1", hash_digest); diff --git a/src/libOpenImageIO/xmp.cpp b/src/libOpenImageIO/xmp.cpp index bc1a777a5e..f919afbfd7 100644 --- a/src/libOpenImageIO/xmp.cpp +++ b/src/libOpenImageIO/xmp.cpp @@ -98,8 +98,8 @@ static XMPtag xmptag[] = { { "tiff:Software", "Software", TypeDesc::STRING, TiffRedundant }, { "exif:ColorSpace", "Exif:ColorSpace", TypeDesc::INT, ExifRedundant }, - { "exif:PixelXDimension", "", TypeDesc::INT, ExifRedundant|TiffRedundant}, - { "exif:PixelYDimension", "", TypeDesc::INT, ExifRedundant|TiffRedundant }, + { "exif:PixelXDimension", "Exif:PixelXDimension", TypeDesc::INT, ExifRedundant|TiffRedundant}, + { "exif:PixelYDimension", "Exif:PixelYDimension", TypeDesc::INT, ExifRedundant|TiffRedundant }, { "exifEX:PhotographicSensitivity", "Exif:ISOSpeedRatings", TypeDesc::INT, ExifRedundant }, { "xmp:CreateDate", "DateTime", TypeDesc::STRING, DateConversion|TiffRedundant }, diff --git a/src/libutil/CMakeLists.txt b/src/libutil/CMakeLists.txt index 526aa6f023..2a159e449b 100644 --- a/src/libutil/CMakeLists.txt +++ b/src/libutil/CMakeLists.txt @@ -51,6 +51,12 @@ function (setup_oiio_util_library targetname) target_link_options(${targetname} PRIVATE ${${PROJECT_NAME}_link_options}) + if (MSVC AND fmt_VERSION VERSION_GREATER_EQUAL 11.0) + # For MSVC, Unicode support requires compiling with /utf-8, and fmt + # needs this. This line adapted from fmt's CMakeLists.txt file. + target_compile_options(${targetname} PUBLIC $<$,$>:/utf-8>) + endif () + target_include_directories (${targetname} PUBLIC $ diff --git a/src/libutil/errorhandler.cpp b/src/libutil/errorhandler.cpp index d0d1a2c385..417ed2aca8 100644 --- a/src/libutil/errorhandler.cpp +++ b/src/libutil/errorhandler.cpp @@ -58,4 +58,15 @@ ErrorHandler::operator()(int errcode, const std::string& msg) fflush(stderr); } + + +void +contract_violation_handler(const char* location, const char* function, + const char* msg) +{ + Strutil::print(stderr, "{} ({}): Contract assertion failed: {}\n", location, + function, msg ? msg : ""); + fflush(stderr); +} + OIIO_NAMESPACE_3_1_END diff --git a/src/libutil/span_test.cpp b/src/libutil/span_test.cpp index 041f59644b..2e030214e4 100644 --- a/src/libutil/span_test.cpp +++ b/src/libutil/span_test.cpp @@ -6,6 +6,8 @@ #include #include +#include +#include #include #include #include @@ -14,6 +16,34 @@ using namespace OIIO; +static int iterations = 100000; +static int ntrials = 5; + +// Intentionally not static so the compiler can't optimize away its value +int Nlen_unknown = 0; + + + +static void +getargs(int argc, char* argv[]) +{ + ArgParse ap; + ap.intro( + "span_test -- unit test and spans for OpenImageIO/span.h\n" OIIO_INTRO_STRING) + .usage("span_test [options]"); + + ap.arg("--iters %d", &iterations) + .help(Strutil::fmt::format("Number of iterations (default: {})", + iterations)); + ap.arg("--trials %d", &ntrials).help("Number of trials"); + + // Fake option to hide from compiler how big it will be + ap.arg("--unknown %d", &Nlen_unknown).hidden(); + + ap.parse_args(argc, (const char**)argv); +} + + void test_span() @@ -457,9 +487,67 @@ test_spanzero() +void +benchmark_span() +{ + Benchmarker bench; + bench.iterations(iterations).trials(ntrials); + const size_t N = 1000; + // bench.work(N); + std::array fstdarr; + std::fill(fstdarr.begin(), fstdarr.end(), 1.0f); + size_t Nlen = Nlen_unknown ? size_t(Nlen_unknown) : N; + bench("pointer operator[]", [&]() { + float* fptr(fstdarr.data()); + float t = 0.0f; + for (size_t i = 0; i < Nlen; ++i) + DoNotOptimize(t += fptr[i]); + }); + bench("std::array operator[]", [&]() { + float t = 0.0f; + for (size_t i = 0; i < Nlen; ++i) + DoNotOptimize(t += fstdarr[i]); + }); + bench("span operator[]", [&]() { + span fspan(fstdarr); + float t = 0.0f; + for (size_t i = 0; i < Nlen; ++i) + DoNotOptimize(t += fspan[i]); + }); + bench("span unsafe indexing", [&]() { + span fspan(fstdarr); + float t = 0.0f; + for (size_t i = 0; i < Nlen; ++i) + DoNotOptimize(t += fspan.data()[i]); + }); + bench("span range", [&]() { + span fspan(fstdarr); + float t = 0.0f; + for (auto x : fspan) + DoNotOptimize(t += x); + }); +} + + + int -main(int /*argc*/, char* /*argv*/[]) +main(int argc, char* argv[]) { + // For the sake of test time, reduce the default number of benchmarking + // trials and iterations for DEBUG, CI, and code coverage builds. Explicit + // use of --iters or --trials will override this, since it comes before + // the getargs() call. + if (Strutil::eval_as_bool(Sysutil::getenv("OpenImageIO_CI")) +#if !defined(NDEBUG) || defined(OIIO_CODE_COVERAGE) + || true +#endif + ) { + iterations /= 10; + ntrials = 1; + } + + getargs(argc, argv); + test_span(); test_span_mutable(); test_span_initlist(); @@ -475,6 +563,7 @@ main(int /*argc*/, char* /*argv*/[]) test_spancpy(); test_spanset(); test_spanzero(); + benchmark_span(); return unit_test_failures; } diff --git a/src/libutil/strutil.cpp b/src/libutil/strutil.cpp index 17060728d7..dc053df65e 100644 --- a/src/libutil/strutil.cpp +++ b/src/libutil/strutil.cpp @@ -152,7 +152,7 @@ c_str(string_view str) // in C++17 string_view. So maybe we'll find ourselves relying on it a // lot less, and therefore the performance hit of doing it foolproof // won't be as onerous. - if (str[str.size()] == 0) // 0-terminated + if (str.data()[str.size()] == 0) // 0-terminated return str.data(); // Rare case: may not be 0-terminated. Bite the bullet and construct a diff --git a/src/libutil/sysutil.cpp b/src/libutil/sysutil.cpp index 03e9127473..95762cd59d 100644 --- a/src/libutil/sysutil.cpp +++ b/src/libutil/sysutil.cpp @@ -73,8 +73,6 @@ # define HAVE_STACKTRACE 1 #endif -OIIO_INTEL_PRAGMA(warning disable 2196) - OIIO_NAMESPACE_3_1_BEGIN diff --git a/src/libutil/typedesc.cpp b/src/libutil/typedesc.cpp index 1c58346a7e..99dc09fed7 100644 --- a/src/libutil/typedesc.cpp +++ b/src/libutil/typedesc.cpp @@ -325,6 +325,8 @@ TypeDesc::fromstring(string_view typestring) t = OIIO::TypeTimeCode; else if (type == "rational") t = OIIO::TypeRational; + else if (type == "urational") + t = OIIO::TypeURational; else if (type == "box2i") t = OIIO::TypeBox2i; else if (type == "box3i") @@ -890,6 +892,12 @@ convert_type(TypeDesc srctype, const void* src, TypeDesc dsttype, void* dst, ((float*)dst)[0] = den ? float(num) / float(den) : 0.0f; return true; } + if (dsttype == TypeFloat && srctype == TypeURational) { + auto num = ((const uint32_t*)src)[0]; + auto den = ((const uint32_t*)src)[1]; + ((float*)dst)[0] = den ? float(num) / float(den) : 0.0f; + return true; + } if (dsttype == TypeFloat && srctype == TypeString) { // Only succeed for a string if it exactly holds something that // exactly parses to a float value. diff --git a/src/libutil/typedesc_test.cpp b/src/libutil/typedesc_test.cpp index b7c2c57ae8..cb6f7b2b3d 100644 --- a/src/libutil/typedesc_test.cpp +++ b/src/libutil/typedesc_test.cpp @@ -69,10 +69,7 @@ test_type(string_view textrep, TypeDesc constructed, tostring_formatting fm(tostring_formatting::STDFORMAT); fm.aggregate_sep = ", "; fm.array_sep = ", "; -#if FMT_VERSION < 70100 - fm.float_fmt = "{:g}"; -#endif - std::string s = tostring(constructed, &value, fm); + std::string s = tostring(constructed, &value, fm); if (valuerep.size()) { OIIO_CHECK_EQUAL(s, valuerep); Strutil::print(" {}\n", s); diff --git a/src/libutil/ustring_test.cpp b/src/libutil/ustring_test.cpp index 6e0a883401..3b4ff692e4 100644 --- a/src/libutil/ustring_test.cpp +++ b/src/libutil/ustring_test.cpp @@ -17,9 +17,7 @@ #include #include -#if FMT_VERSION >= 90000 -# include -#endif +#include using namespace OIIO; diff --git a/src/oiiotool/expressions.cpp b/src/oiiotool/expressions.cpp index fbb8568666..edc68fa88f 100644 --- a/src/oiiotool/expressions.cpp +++ b/src/oiiotool/expressions.cpp @@ -156,7 +156,7 @@ Oiiotool::express_parse_atom(const string_view expr, string_view& s, if (Strutil::parse_prefix(s, "TOP")) { img = curimg; } else if (Strutil::parse_prefix(s, "BOTTOM")) { - img = (image_stack.size() <= 1) ? curimg : image_stack[0]; + img = image_stack.empty() ? curimg : image_stack[0]; } else if (Strutil::parse_prefix(s, "IMG[")) { std::string until_bracket = Strutil::parse_until(s, "]"); if (until_bracket.empty() || !Strutil::parse_char(s, ']')) { diff --git a/src/png.imageio/png_pvt.h b/src/png.imageio/png_pvt.h index f7b6bbbb62..3631910192 100644 --- a/src/png.imageio/png_pvt.h +++ b/src/png.imageio/png_pvt.h @@ -226,12 +226,7 @@ read_info(png_structp& sp, png_infop& ip, int& bit_depth, int& color_type, if (png_get_sRGB(sp, ip, &srgb_intent)) { spec.attribute("oiio:ColorSpace", "srgb_rec709_scene"); } else if (png_get_gAMA(sp, ip, &gamma) && gamma > 0.0) { - // Round gamma to the nearest hundredth to prevent stupid - // precision choices and make it easier for apps to make - // decisions based on known gamma values. For example, you want - // 2.2, not 2.19998. float g = float(1.0 / gamma); - g = roundf(100.0f * g) / 100.0f; set_colorspace_rec709_gamma(spec, g); } else { // If there's no info at all, assume sRGB. diff --git a/src/png.imageio/pngoutput.cpp b/src/png.imageio/pngoutput.cpp index 00e11947c9..7d06eca25f 100644 --- a/src/png.imageio/pngoutput.cpp +++ b/src/png.imageio/pngoutput.cpp @@ -38,9 +38,6 @@ class PNGOutput final : public ImageOutput { bool write_scanlines(int ybegin, int yend, int z, TypeDesc format, const void* data, stride_t xstride = AutoStride, stride_t ystride = AutoStride) override; - bool write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, - stride_t zstride) override; private: std::string m_filename; ///< Stash the filename @@ -55,7 +52,6 @@ class PNGOutput final : public ImageOutput { float m_gamma = 1.0f; ///< Gamma to use for alpha conversion std::vector m_scratch; std::vector m_pngtext; - std::vector m_tilebuffer; bool m_err = false; // Initialize private members to pre-opened state @@ -240,11 +236,6 @@ PNGOutput::open(const std::string& name, const ImageSpec& userspec, m_convert_alpha = m_spec.alpha_channel != -1 && !m_spec.get_int_attribute("oiio:UnassociatedAlpha", 0); - // If user asked for tiles -- which this format doesn't support, emulate - // it by buffering the whole image. - if (m_spec.tile_width && m_spec.tile_height) - m_tilebuffer.resize(m_spec.image_bytes()); - return true; } @@ -258,15 +249,6 @@ PNGOutput::close() return true; } - bool ok = true; - if (m_spec.tile_width) { - // Handle tile emulation -- output the buffered pixels - OIIO_ASSERT(m_tilebuffer.size()); - ok &= write_scanlines(m_spec.y, m_spec.y + m_spec.height, 0, - m_spec.format, &m_tilebuffer[0]); - std::vector().swap(m_tilebuffer); - } - if (m_png) { PNG_pvt::write_end(m_png, m_info); if (m_png || m_info) @@ -276,7 +258,7 @@ PNGOutput::close() } init(); // re-initialize - return ok; + return true; } @@ -457,14 +439,4 @@ PNGOutput::write_scanlines(int ybegin, int yend, int z, TypeDesc format, -bool -PNGOutput::write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, stride_t zstride) -{ - // Emulate tiles by buffering the whole image - return copy_tile_to_image_buffer(x, y, z, format, data, xstride, ystride, - zstride, &m_tilebuffer[0]); -} - - OIIO_PLUGIN_NAMESPACE_END diff --git a/src/pnm.imageio/pnmoutput.cpp b/src/pnm.imageio/pnmoutput.cpp index 54596a3afa..1bb94b9677 100644 --- a/src/pnm.imageio/pnmoutput.cpp +++ b/src/pnm.imageio/pnmoutput.cpp @@ -30,9 +30,6 @@ class PNMOutput final : public ImageOutput { bool write_scanlines(int ybegin, int yend, int z, TypeDesc format, const void* data, stride_t xstride = AutoStride, stride_t ystride = AutoStride) override; - bool write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, - stride_t zstride) override; private: std::string m_filename; // Stash the filename @@ -42,7 +39,6 @@ class PNMOutput final : public ImageOutput { unsigned int m_dither; std::vector m_scratch; - std::vector m_tilebuffer; void init(void) { ioproxy_clear(); } @@ -310,10 +306,6 @@ PNMOutput::open(const std::string& name, const ImageSpec& userspec, ok &= iowritefmt("{}\n", scale); } } - // If user asked for tiles -- which this format doesn't support, emulate - // it by buffering the whole image. - if (m_spec.tile_width && m_spec.tile_height) - m_tilebuffer.resize(m_spec.image_bytes()); return ok; } @@ -326,17 +318,8 @@ PNMOutput::close() if (!ioproxy_opened()) // already closed return true; - bool ok = true; - if (m_spec.tile_width) { - // Handle tile emulation -- output the buffered pixels - OIIO_DASSERT(m_tilebuffer.size()); - ok &= ImageOutput::write_scanlines(m_spec.y, m_spec.y + m_spec.height, - 0, m_spec.format, &m_tilebuffer[0]); - m_tilebuffer.shrink_to_fit(); - } - init(); - return ok; + return true; } @@ -422,14 +405,4 @@ PNMOutput::write_scanlines(int ybegin, int yend, int z, TypeDesc format, -bool -PNMOutput::write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, stride_t zstride) -{ - // Emulate tiles by buffering the whole image - return copy_tile_to_image_buffer(x, y, z, format, data, xstride, ystride, - zstride, &m_tilebuffer[0]); -} - - OIIO_PLUGIN_NAMESPACE_END diff --git a/src/rla.imageio/rlainput.cpp b/src/rla.imageio/rlainput.cpp index bd1215dacc..4befaffe2b 100644 --- a/src/rla.imageio/rlainput.cpp +++ b/src/rla.imageio/rlainput.cpp @@ -397,11 +397,6 @@ RLAInput::seek_subimage(int subimage, int miplevel) float gamma = Strutil::from_string(m_rla.Gamma); if (gamma > 0.f) { - // Round gamma to the nearest hundredth to prevent stupid - // precision choices and make it easier for apps to make - // decisions based on known gamma values. For example, you want - // 2.2, not 2.19998. - gamma = roundf(100.0 * gamma) / 100.0f; set_colorspace_rec709_gamma(m_spec, gamma); } diff --git a/src/rla.imageio/rlaoutput.cpp b/src/rla.imageio/rlaoutput.cpp index fec2736d51..6afcad128f 100644 --- a/src/rla.imageio/rlaoutput.cpp +++ b/src/rla.imageio/rlaoutput.cpp @@ -35,16 +35,12 @@ class RLAOutput final : public ImageOutput { bool close() override; bool write_scanline(int y, int z, TypeDesc format, const void* data, stride_t xstride) override; - bool write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, - stride_t zstride) override; private: std::vector m_scratch; RLAHeader m_rla; ///< Wavefront RLA header std::vector m_sot; ///< Scanline offset table std::vector m_rle; ///< Run record buffer for RLE - std::vector m_tilebuffer; unsigned int m_dither; // Initialize private members to pre-opened state @@ -340,11 +336,6 @@ RLAOutput::open(const std::string& name, const ImageSpec& userspec, m_sot.resize(m_spec.height, (int32_t)0); write(&m_sot[0], m_sot.size()); - // If user asked for tiles -- which this format doesn't support, emulate - // it by buffering the whole image. - if (m_spec.tile_width && m_spec.tile_height) - m_tilebuffer.resize(m_spec.image_bytes()); - return true; } @@ -382,22 +373,13 @@ RLAOutput::close() return true; } - bool ok = true; - if (m_spec.tile_width) { - // Handle tile emulation -- output the buffered pixels - OIIO_DASSERT(m_tilebuffer.size()); - ok &= write_scanlines(m_spec.y, m_spec.y + m_spec.height, 0, - m_spec.format, &m_tilebuffer[0]); - std::vector().swap(m_tilebuffer); - } - // Now that all scanlines have been output, return to write the // correct scanline offset table to file and close the stream. ioseek(sizeof(RLAHeader)); write(m_sot.data(), m_sot.size()); init(); // re-initialize - return ok; + return true; } @@ -545,14 +527,4 @@ RLAOutput::write_scanline(int y, int z, TypeDesc format, const void* data, -bool -RLAOutput::write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, stride_t zstride) -{ - // Emulate tiles by buffering the whole image - return copy_tile_to_image_buffer(x, y, z, format, data, xstride, ystride, - zstride, &m_tilebuffer[0]); -} - - OIIO_PLUGIN_NAMESPACE_END diff --git a/src/sgi.imageio/sgioutput.cpp b/src/sgi.imageio/sgioutput.cpp index 2d3c9f358d..0eb0928cd3 100644 --- a/src/sgi.imageio/sgioutput.cpp +++ b/src/sgi.imageio/sgioutput.cpp @@ -26,12 +26,19 @@ class SgiOutput final : public ImageOutput { std::string m_filename; std::vector m_scratch; unsigned int m_dither; - std::vector m_tilebuffer; + bool m_want_rle; + std::vector m_uncompressed_image; void init() { ioproxy_clear(); } bool create_and_write_header(); + bool write_scanline_raw(int y, const unsigned char* data); + bool write_scanline_rle(int y, const unsigned char* data, int64_t& offset, + std::vector& start_table, + std::vector& length_table); + bool write_buffered_pixels(); + /// Helper - write, with error detection template bool fwrite(const T* buf, size_t itemsize = sizeof(T), size_t nitems = 1) @@ -85,10 +92,12 @@ SgiOutput::open(const std::string& name, const ImageSpec& spec, OpenMode mode) ? m_spec.get_int_attribute("oiio:dither", 0) : 0; + m_want_rle = m_spec.get_string_attribute("compression") == "rle"; + // If user asked for tiles -- which this format doesn't support, emulate - // it by buffering the whole image. - if (m_spec.tile_width && m_spec.tile_height) - m_tilebuffer.resize(m_spec.image_bytes()); + // it by buffering the whole image. RLE is treated similarly. + if (m_want_rle || (m_spec.tile_width && m_spec.tile_height)) + m_uncompressed_image.resize(m_spec.image_bytes()); return create_and_write_header(); } @@ -102,32 +111,57 @@ SgiOutput::write_scanline(int y, int z, TypeDesc format, const void* data, y = m_spec.height - y - 1; data = to_native_scanline(format, data, xstride, m_scratch, m_dither, y, z); + // If we are writing RLE data, just copy into the uncompressed buffer + if (m_want_rle) { + const auto scaneline_size = m_spec.scanline_bytes(); + memcpy(&m_uncompressed_image[y * scaneline_size], data, scaneline_size); + + return true; + } + + return write_scanline_raw(y, (const unsigned char*)data); +} + + + +bool +SgiOutput::write_tile(int x, int y, int z, TypeDesc format, const void* data, + stride_t xstride, stride_t ystride, stride_t zstride) +{ + // Emulate tiles by buffering the whole image + return copy_tile_to_image_buffer(x, y, z, format, data, xstride, ystride, + zstride, &m_uncompressed_image[0]); +} + + + +bool +SgiOutput::write_scanline_raw(int y, const unsigned char* data) +{ // In SGI format all channels are saved to file separately: first, all // channel 1 scanlines are saved, then all channel2 scanlines are saved // and so on. - // - // Note that since SGI images are pretty archaic and most probably - // people won't be too picky about full flexibility writing them, we - // content ourselves with only writing uncompressed data, and don't - // attempt to write with RLE encoding. size_t bpc = m_spec.format.size(); // bytes per channel std::unique_ptr channeldata( new unsigned char[m_spec.width * bpc]); for (int64_t c = 0; c < m_spec.nchannels; ++c) { - unsigned char* cdata = (unsigned char*)data + c * bpc; + const unsigned char* cdata = data + c * bpc; for (int64_t x = 0; x < m_spec.width; ++x) { channeldata[x * bpc] = cdata[0]; if (bpc == 2) channeldata[x * bpc + 1] = cdata[1]; cdata += m_spec.nchannels * bpc; // advance to next pixel } + if (bpc == 2 && littleendian()) swap_endian((unsigned short*)&channeldata[0], m_spec.width); + ptrdiff_t scanline_offset = sgi_pvt::SGI_HEADER_LEN + ptrdiff_t(c * m_spec.height + y) * m_spec.width * bpc; + ioseek(scanline_offset); if (!iowrite(&channeldata[0], 1, m_spec.width * bpc)) { return false; @@ -139,13 +173,179 @@ SgiOutput::write_scanline(int y, int z, TypeDesc format, const void* data, +static bool +data_equals(const unsigned char* data, int bpc, imagesize_t off1, + imagesize_t off2) +{ + if (bpc == 1) { + return data[off1] == data[off2]; + } else { + return data[off1] == data[off2] && data[off1 + 1] == data[off2 + 1]; + } +} + + + +static void +data_set(unsigned char* data, int bpc, imagesize_t off, + const unsigned char* val) +{ + if (bpc == 1) { + data[off] = val[0]; + } else { + data[off] = val[1]; + data[off + 1] = val[0]; + } +} + + + +static void +data_set(unsigned char* data, int bpc, imagesize_t off, const short val) +{ + if (bpc == 1) { + data[off] = static_cast(val); + } else { + data[off] = static_cast(val >> 8); + data[off + 1] = static_cast(val & 0xFF); + } +} + + + bool -SgiOutput::write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, stride_t zstride) +SgiOutput::write_scanline_rle(int y, const unsigned char* data, int64_t& offset, + std::vector& offset_table, + std::vector& length_table) { - // Emulate tiles by buffering the whole image - return copy_tile_to_image_buffer(x, y, z, format, data, xstride, ystride, - zstride, &m_tilebuffer[0]); + const size_t bpc = m_spec.format.size(); // bytes per channel + const size_t xstride = m_spec.nchannels * bpc; + const imagesize_t scanline_bytes = m_spec.scanline_bytes(); + + // Account for the worst case length when every pixel is different + m_scratch.resize(bpc * (m_spec.width + (m_spec.width / 127 + 2))); + + for (int64_t c = 0; c < m_spec.nchannels; ++c) { + const unsigned char* cdata = data + c * bpc; + + imagesize_t out = 0; + imagesize_t pos = 0; + while (pos < scanline_bytes) { + imagesize_t start = pos; + // Find the first run meeting a minimum length of 3 + imagesize_t ahead_1 = pos + xstride; + imagesize_t ahead_2 = pos + xstride * 2; + while (ahead_2 < scanline_bytes + && (!data_equals(cdata, bpc, ahead_1, ahead_2) + || !data_equals(cdata, bpc, pos, ahead_1))) { + pos += xstride; + ahead_1 += xstride; + ahead_2 += xstride; + } + if (ahead_2 >= scanline_bytes) { + // No more runs, just dump the rest as literals + pos = scanline_bytes; + } + int count = int((pos - start) / xstride); + while (count) { + int todo = (count > 127) ? 127 : count; + count -= todo; + data_set(m_scratch.data(), bpc, out, 0x80 | todo); + out += bpc; + while (todo) { + data_set(m_scratch.data(), bpc, out, cdata + start); + out += bpc; + start += xstride; + todo -= 1; + } + } + start = pos; + if (start >= scanline_bytes) + break; + pos += xstride; + while (pos < scanline_bytes + && data_equals(cdata, bpc, start, pos)) { + pos += xstride; + } + count = int((pos - start) / xstride); + while (count) { + int curr_run = (count > 127) ? 127 : count; + count -= curr_run; + data_set(m_scratch.data(), bpc, out, curr_run); + out += bpc; + data_set(m_scratch.data(), bpc, out, cdata + start); + out += bpc; + } + } + data_set(m_scratch.data(), bpc, out, short(0)); + out += bpc; + + // Fill in details about the scanline + const int table_index = c * m_spec.height + y; + offset_table[table_index] = static_cast(offset); + length_table[table_index] = static_cast(out); + + // Write the compressed data + if (!iowrite(&m_scratch[0], 1, out)) + return false; + offset += out; + } + + return true; +} + + + +bool +SgiOutput::write_buffered_pixels() +{ + OIIO_ASSERT(m_uncompressed_image.size()); + + const auto scanline_bytes = m_spec.scanline_bytes(); + if (m_want_rle) { + // Prepare RLE tables + const int64_t table_size = m_spec.height * m_spec.nchannels; + const int64_t table_size_bytes = table_size * sizeof(int); + std::vector offset_table; + std::vector length_table; + offset_table.resize(table_size); + length_table.resize(table_size); + + // Skip over the tables and start at the data area + int64_t offset = sgi_pvt::SGI_HEADER_LEN + 2 * table_size_bytes; + ioseek(offset); + + // Write RLE compressed data + for (int y = 0; y < m_spec.height; ++y) { + const unsigned char* scanline_data + = &m_uncompressed_image[y * scanline_bytes]; + if (!write_scanline_rle(y, scanline_data, offset, offset_table, + length_table)) + return false; + } + + // Write the tables now that they're filled in with offsets/lengths + ioseek(sgi_pvt::SGI_HEADER_LEN); + if (littleendian()) { + swap_endian(&offset_table[0], table_size); + swap_endian(&length_table[0], table_size); + } + if (!iowrite(&offset_table[0], 1, table_size_bytes)) + return false; + if (!iowrite(&length_table[0], 1, table_size_bytes)) + return false; + + } else { + // Write raw data + for (int y = 0; y < m_spec.height; ++y) { + unsigned char* scanline_data + = &m_uncompressed_image[y * scanline_bytes]; + if (!write_scanline_raw(y, scanline_data)) + return false; + } + } + + return true; } @@ -160,15 +360,22 @@ SgiOutput::close() bool ok = true; if (m_spec.tile_width) { - // Handle tile emulation -- output the buffered pixels - OIIO_ASSERT(m_tilebuffer.size()); + // We've been emulating tiles; now dump as scanlines. + OIIO_ASSERT(m_uncompressed_image.size()); ok &= write_scanlines(m_spec.y, m_spec.y + m_spec.height, 0, - m_spec.format, &m_tilebuffer[0]); - m_tilebuffer.clear(); - m_tilebuffer.shrink_to_fit(); + m_spec.format, &m_uncompressed_image[0]); } + // If we want RLE encoding or we were tiled, output all the processed scanlines now. + if (ok && (m_want_rle || m_spec.tile_width)) { + ok &= write_buffered_pixels(); + } + + m_uncompressed_image.clear(); + m_uncompressed_image.shrink_to_fit(); + init(); + return ok; } @@ -179,7 +386,7 @@ SgiOutput::create_and_write_header() { sgi_pvt::SgiHeader sgi_header; sgi_header.magic = sgi_pvt::SGI_MAGIC; - sgi_header.storage = sgi_pvt::VERBATIM; + sgi_header.storage = m_want_rle ? sgi_pvt::RLE : sgi_pvt::VERBATIM; sgi_header.bpc = m_spec.format.size(); if (m_spec.height == 1 && m_spec.nchannels == 1) diff --git a/src/targa.imageio/targainput.cpp b/src/targa.imageio/targainput.cpp index 984f772ffa..45453965ea 100644 --- a/src/targa.imageio/targainput.cpp +++ b/src/targa.imageio/targainput.cpp @@ -435,11 +435,6 @@ TGAInput::read_tga2_header() if (bigendian()) swap_endian(&buf.s[0], 2); float gamma = (float)buf.s[0] / (float)buf.s[1]; - // Round gamma to the nearest hundredth to prevent stupid - // precision choices and make it easier for apps to make - // decisions based on known gamma values. For example, you want - // 2.2, not 2.19998. - gamma = roundf(100.0 * gamma) / 100.0f; set_colorspace_rec709_gamma(m_spec, gamma); } diff --git a/src/targa.imageio/targaoutput.cpp b/src/targa.imageio/targaoutput.cpp index b580da9907..ef75e2c0bb 100644 --- a/src/targa.imageio/targaoutput.cpp +++ b/src/targa.imageio/targaoutput.cpp @@ -37,9 +37,6 @@ class TGAOutput final : public ImageOutput { bool close() override; bool write_scanline(int y, int z, TypeDesc format, const void* data, stride_t xstride) override; - bool write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, - stride_t zstride) override; bool set_thumbnail(const ImageBuf& thumb) override; private: @@ -50,7 +47,6 @@ class TGAOutput final : public ImageOutput { std::vector m_scratch; int m_idlen; ///< Length of the TGA ID block unsigned int m_dither; - std::vector m_tilebuffer; ImageBuf m_thumb; // Initialize private members to pre-opened state @@ -239,11 +235,6 @@ TGAOutput::open(const std::string& name, const ImageSpec& userspec, } } - // If user asked for tiles -- which this format doesn't support, emulate - // it by buffering the whole image. - if (m_spec.tile_width && m_spec.tile_height) - m_tilebuffer.resize(m_spec.image_bytes()); - return true; } @@ -421,16 +412,7 @@ TGAOutput::close() return true; } - bool ok = true; - if (m_spec.tile_width) { - // Handle tile emulation -- output the buffered pixels - OIIO_ASSERT(m_tilebuffer.size()); - ok &= write_scanlines(m_spec.y, m_spec.y + m_spec.height, 0, - m_spec.format, &m_tilebuffer[0]); - m_tilebuffer.shrink_to_fit(); - } - - ok &= write_tga20_data_fields(); + bool ok = write_tga20_data_fields(); init(); // re-initialize return ok; @@ -681,17 +663,6 @@ TGAOutput::write_scanline(int y, int z, TypeDesc format, const void* data, -bool -TGAOutput::write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, stride_t zstride) -{ - // Emulate tiles by buffering the whole image - return copy_tile_to_image_buffer(x, y, z, format, data, xstride, ystride, - zstride, &m_tilebuffer[0]); -} - - - bool TGAOutput::set_thumbnail(const ImageBuf& thumb) { diff --git a/src/tiff.imageio/tiffinput.cpp b/src/tiff.imageio/tiffinput.cpp index ac360d2c32..8233de9284 100644 --- a/src/tiff.imageio/tiffinput.cpp +++ b/src/tiff.imageio/tiffinput.cpp @@ -280,7 +280,8 @@ class TIFFInput final : public ImageInput { OIIO_NODISCARD bool safe_tiffgetfield(string_view name OIIO_MAYBE_UNUSED, int tag, - TypeDesc expected, void* dest) + TypeDesc expected, void* dest, + const uint32_t* count = nullptr) { TypeDesc type = tiffgetfieldtype(tag); // Caller expects a specific type and the tag doesn't match? Punt. @@ -295,6 +296,11 @@ class TIFFInput final : public ImageInput { int readcount = TIFFFieldReadCount(field); if (!passcount && readcount > 0) { return TIFFGetField(m_tif, tag, dest); + } else if (passcount && readcount <= 0) { + uint32_t mycount = 0; + if (!count) + count = &mycount; + return TIFFGetField(m_tif, tag, count, dest); } // OIIO::debugfmt(" stgf {} tag {} {} datatype {} passcount {} readcount {}\n", // name, tag, type, int(TIFFFieldDataType(field)), passcount, readcount); @@ -396,21 +402,48 @@ class TIFFInput final : public ImageInput { // add it in the obvious way to m_spec under the name 'oiioname'. void find_tag(int tifftag, TIFFDataType tifftype, string_view oiioname) { + if (tifftype == TIFF_NOTYPE) + return; // NOTYPE is a signal that should skip it auto info = find_field(tifftag, tifftype); if (!info) { // Something has gone wrong, libtiff doesn't think the field type // is the same as we do. return; } - if (tifftype == TIFF_ASCII) + tifftype = TIFFFieldDataType(info); + int count = TIFFFieldReadCount(info); + if (tifftype == TIFF_ASCII) { get_string_attribute(oiioname, tifftag); - else if (tifftype == TIFF_SHORT) + return; + } else if (tifftype == TIFF_SHORT) { get_short_attribute(oiioname, tifftag); - else if (tifftype == TIFF_LONG) + return; + } else if (tifftype == TIFF_LONG) { get_int_attribute(oiioname, tifftag); - else if (tifftype == TIFF_RATIONAL || tifftype == TIFF_SRATIONAL - || tifftype == TIFF_FLOAT || tifftype == TIFF_DOUBLE) + return; + } else if (tifftype == TIFF_RATIONAL || tifftype == TIFF_SRATIONAL + || tifftype == TIFF_FLOAT || tifftype == TIFF_DOUBLE) { get_float_attribute(oiioname, tifftag); + return; + } + // special cases follow + if (tifftype == TIFF_UNDEFINED) { + if ((tifftag == EXIF_EXIFVERSION || tifftag == EXIF_FLASHPIXVERSION) + && count == 4) { + char* ptr = nullptr; + if (safe_tiffgetfield(oiioname, tifftag, TypeUnknown, &ptr) + && ptr && ptr[0]) { + std::string str(ptr, 4); + m_spec.attribute(oiioname, str); + } + return; + } + } +#if 0 + print("Unhandled TIFF tag {} type {} count {} pass {} for {}\n", + tifftag, int(tifftype), count, TIFFFieldPassCount(info), + oiioname); +#endif } // If we're at scanline y, where does the next strip start? @@ -671,7 +704,7 @@ static std::pair tiff_input_compressions[] = { { COMPRESSION_NEXT, "next" }, // NeXT 2-bit RLE { COMPRESSION_CCITTRLEW, "ccittrle2" }, // #1 w/ word alignment { COMPRESSION_PACKBITS, "packbits" }, // Macintosh RLE - { COMPRESSION_THUNDERSCAN, "thunderscan" }, // ThundeScan RLE + { COMPRESSION_THUNDERSCAN, "thunderscan" }, // ThunderScan RLE { COMPRESSION_IT8CTPAD, "IT8CTPAD" }, // IT8 CT w/ patting { COMPRESSION_IT8LW, "IT8LW" }, // IT8 linework RLE { COMPRESSION_IT8MP, "IT8MP" }, // IT8 monochrome picture diff --git a/src/tiff.imageio/tiffoutput.cpp b/src/tiff.imageio/tiffoutput.cpp index efac72345b..eb0291f1f1 100644 --- a/src/tiff.imageio/tiffoutput.cpp +++ b/src/tiff.imageio/tiffoutput.cpp @@ -149,8 +149,7 @@ class TIFFOutput final : public ImageOutput { void fix_bitdepth(void* data, int nvals); // Add a parameter to the output - bool put_parameter(const std::string& name, TypeDesc type, - const void* data); + bool put_parameter(const ParamValue& metadata); bool write_exif_data(); // Make our best guess about whether the spec is describing data that @@ -921,10 +920,8 @@ TIFFOutput::open(const std::string& name, const ImageSpec& userspec, } // Deal with all other params - for (size_t p = 0; p < m_spec.extra_attribs.size(); ++p) - put_parameter(m_spec.extra_attribs[p].name().string(), - m_spec.extra_attribs[p].type(), - m_spec.extra_attribs[p].data()); + for (const auto& p : m_spec.extra_attribs) + put_parameter(p); if (m_spec.get_int_attribute("tiff:write_iptc")) { // Enable IPTC block writing only if "tiff_write_iptc" hint is explicitly @@ -956,120 +953,125 @@ TIFFOutput::open(const std::string& name, const ImageSpec& userspec, +inline int +resunit_to_code(string_view s) +{ + if (Strutil::iequals(s, "none")) + return RESUNIT_NONE; + else if (Strutil::iequals(s, "in") || Strutil::iequals(s, "inch")) + return RESUNIT_INCH; + else if (Strutil::iequals(s, "cm")) + return RESUNIT_CENTIMETER; + return 0; +} + + + bool -TIFFOutput::put_parameter(const std::string& name, TypeDesc type, - const void* data) +TIFFOutput::put_parameter(const ParamValue& param) { - if (!data || (type == TypeString && *(char**)data == nullptr)) { + ustring name = param.uname(); + TypeDesc type = param.type(); + if (!param.data() + || (type == TypeString && *(char**)param.data() == nullptr)) { // we got a null pointer, don't set the field return false; } - if (Strutil::iequals(name, "Artist") && type == TypeDesc::STRING) { - TIFFSetField(m_tif, TIFFTAG_ARTIST, *(char**)data); + if (Strutil::iequals(name, "Artist")) { + TIFFSetField(m_tif, TIFFTAG_ARTIST, param.get_string().c_str()); return true; } - if (Strutil::iequals(name, "Copyright") && type == TypeDesc::STRING) { - TIFFSetField(m_tif, TIFFTAG_COPYRIGHT, *(char**)data); + if (Strutil::iequals(name, "Copyright")) { + TIFFSetField(m_tif, TIFFTAG_COPYRIGHT, param.get_string().c_str()); return true; } - if (Strutil::iequals(name, "DateTime") && type == TypeDesc::STRING) { - TIFFSetField(m_tif, TIFFTAG_DATETIME, *(char**)data); + if (Strutil::iequals(name, "DateTime") && type == TypeString) { + TIFFSetField(m_tif, TIFFTAG_DATETIME, param.get_string().c_str()); return true; } - if ((Strutil::iequals(name, "name") - || Strutil::iequals(name, "DocumentName")) - && type == TypeDesc::STRING) { - TIFFSetField(m_tif, TIFFTAG_DOCUMENTNAME, *(char**)data); + if (Strutil::iequals(name, "name") + || Strutil::iequals(name, "DocumentName")) { + TIFFSetField(m_tif, TIFFTAG_DOCUMENTNAME, param.get_string().c_str()); return true; } - if (Strutil::iequals(name, "fovcot") && type == TypeDesc::FLOAT) { - double d = *(float*)data; - TIFFSetField(m_tif, TIFFTAG_PIXAR_FOVCOT, d); + if (Strutil::iequals(name, "fovcot") && type == TypeFloat) { + TIFFSetField(m_tif, TIFFTAG_PIXAR_FOVCOT, param.get_float()); return true; } - if ((Strutil::iequals(name, "host") - || Strutil::iequals(name, "HostComputer")) - && type == TypeDesc::STRING) { - TIFFSetField(m_tif, TIFFTAG_HOSTCOMPUTER, *(char**)data); + if (Strutil::iequals(name, "host") + || Strutil::iequals(name, "HostComputer")) { + TIFFSetField(m_tif, TIFFTAG_HOSTCOMPUTER, param.get_string().c_str()); return true; } if ((Strutil::iequals(name, "description") || Strutil::iequals(name, "ImageDescription")) - && type == TypeDesc::STRING) { - TIFFSetField(m_tif, TIFFTAG_IMAGEDESCRIPTION, *(char**)data); + && type == TypeString) { + TIFFSetField(m_tif, TIFFTAG_IMAGEDESCRIPTION, + param.get_string().c_str()); return true; } - if (Strutil::iequals(name, "tiff:Predictor") && type == TypeDesc::INT) { - m_predictor = *(int*)data; + if (Strutil::iequals(name, "tiff:Predictor")) { + m_predictor = param.get_int(); TIFFSetField(m_tif, TIFFTAG_PREDICTOR, m_predictor); return true; } - if (Strutil::iequals(name, "ResolutionUnit") && type == TypeDesc::STRING) { - const char* s = *(char**)data; - bool ok = true; - if (Strutil::iequals(s, "none")) - TIFFSetField(m_tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_NONE); - else if (Strutil::iequals(s, "in") || Strutil::iequals(s, "inch")) - TIFFSetField(m_tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_INCH); - else if (Strutil::iequals(s, "cm")) - TIFFSetField(m_tif, TIFFTAG_RESOLUTIONUNIT, RESUNIT_CENTIMETER); - else - ok = false; - return ok; + if (Strutil::iequals(name, "ResolutionUnit") && type == TypeString) { + if (int r = resunit_to_code(param.get_string())) { + TIFFSetField(m_tif, TIFFTAG_RESOLUTIONUNIT, r); + return true; + } + return false; } if (Strutil::iequals(name, "tiff:RowsPerStrip") && !m_spec.tile_width /* don't set rps for tiled files */ && m_planarconfig == PLANARCONFIG_CONTIG /* only for contig */) { - if (type == TypeDesc::INT) { - m_rowsperstrip = *(int*)data; - } else if (type == TypeDesc::STRING) { - // Back-compatibility with Entropy and PRMan - m_rowsperstrip = Strutil::stoi(*(char**)data); - } else { + int rps = param.get_int(); + if (rps <= 0) return false; - } - m_rowsperstrip = clamp(m_rowsperstrip, 1, m_spec.height); + m_rowsperstrip = clamp(rps, 1, m_spec.height); TIFFSetField(m_tif, TIFFTAG_ROWSPERSTRIP, m_rowsperstrip); return true; } - if (Strutil::iequals(name, "Make") && type == TypeDesc::STRING) { - TIFFSetField(m_tif, TIFFTAG_MAKE, *(char**)data); + if (Strutil::iequals(name, "Make")) { + TIFFSetField(m_tif, TIFFTAG_MAKE, param.get_string().c_str()); return true; } - if (Strutil::iequals(name, "Model") && type == TypeDesc::STRING) { - TIFFSetField(m_tif, TIFFTAG_MODEL, *(char**)data); + if (Strutil::iequals(name, "Model")) { + TIFFSetField(m_tif, TIFFTAG_MODEL, param.get_string().c_str()); return true; } - if (Strutil::iequals(name, "Software") && type == TypeDesc::STRING) { - TIFFSetField(m_tif, TIFFTAG_SOFTWARE, *(char**)data); + if (Strutil::iequals(name, "Software")) { + TIFFSetField(m_tif, TIFFTAG_SOFTWARE, param.get_string().c_str()); return true; } - if (Strutil::iequals(name, "tiff:SubFileType") && type == TypeDesc::INT) { - TIFFSetField(m_tif, TIFFTAG_SUBFILETYPE, *(int*)data); + if (Strutil::iequals(name, "tiff:SubFileType")) { + TIFFSetField(m_tif, TIFFTAG_SUBFILETYPE, param.get_int()); return true; } - if (Strutil::iequals(name, "textureformat") && type == TypeDesc::STRING) { - TIFFSetField(m_tif, TIFFTAG_PIXAR_TEXTUREFORMAT, *(char**)data); + if (Strutil::iequals(name, "textureformat")) { + TIFFSetField(m_tif, TIFFTAG_PIXAR_TEXTUREFORMAT, + param.get_string().c_str()); return true; } - if (Strutil::iequals(name, "wrapmodes") && type == TypeDesc::STRING) { - TIFFSetField(m_tif, TIFFTAG_PIXAR_WRAPMODES, *(char**)data); + if (Strutil::iequals(name, "wrapmodes")) { + TIFFSetField(m_tif, TIFFTAG_PIXAR_WRAPMODES, + param.get_string().c_str()); return true; } if (Strutil::iequals(name, "worldtocamera") && type == TypeMatrix) { - TIFFSetField(m_tif, TIFFTAG_PIXAR_MATRIX_WORLDTOCAMERA, data); + TIFFSetField(m_tif, TIFFTAG_PIXAR_MATRIX_WORLDTOCAMERA, param.data()); return true; } if (Strutil::iequals(name, "worldtoscreen") && type == TypeMatrix) { - TIFFSetField(m_tif, TIFFTAG_PIXAR_MATRIX_WORLDTOSCREEN, data); + TIFFSetField(m_tif, TIFFTAG_PIXAR_MATRIX_WORLDTOSCREEN, param.data()); return true; } - if (Strutil::iequals(name, "XResolution") && type == TypeDesc::FLOAT) { - TIFFSetField(m_tif, TIFFTAG_XRESOLUTION, *(float*)data); + if (Strutil::iequals(name, "XResolution")) { + TIFFSetField(m_tif, TIFFTAG_XRESOLUTION, param.get_float()); return true; } - if (Strutil::iequals(name, "YResolution") && type == TypeDesc::FLOAT) { - TIFFSetField(m_tif, TIFFTAG_YRESOLUTION, *(float*)data); + if (Strutil::iequals(name, "YResolution")) { + TIFFSetField(m_tif, TIFFTAG_YRESOLUTION, param.get_float()); return true; } return false; @@ -1134,27 +1136,84 @@ TIFFOutput::write_exif_data() int tag, tifftype, count; if (exif_tag_lookup(p.name(), tag, tifftype, count) && tifftype != TIFF_NOTYPE) { + bool ok = false; + bool handled = false; + // Some special cases first if (tag == EXIF_SECURITYCLASSIFICATION || tag == EXIF_IMAGEHISTORY - || tag == EXIF_PHOTOGRAPHICSENSITIVITY) + || tag == EXIF_PHOTOGRAPHICSENSITIVITY) { continue; // libtiff doesn't understand these - bool ok = false; - if (tifftype == TIFF_ASCII) { - ok = TIFFSetField(m_tif, tag, *(char**)p.data()); - } else if ((tifftype == TIFF_SHORT || tifftype == TIFF_LONG) - && p.type() == TypeDesc::SHORT) { - ok = TIFFSetField(m_tif, tag, (int)*(short*)p.data()); - } else if ((tifftype == TIFF_SHORT || tifftype == TIFF_LONG) - && p.type() == TypeDesc::INT) { - ok = TIFFSetField(m_tif, tag, *(int*)p.data()); - } else if ((tifftype == TIFF_RATIONAL || tifftype == TIFF_SRATIONAL) - && p.type() == TypeDesc::FLOAT) { - ok = TIFFSetField(m_tif, tag, *(float*)p.data()); + } + if (tag == TIFFTAG_RESOLUTIONUNIT && p.type() == TypeString) { + // OIIO stores resolution unit as a string, but libtiff wants + // it as a short code, so we have to convert. + if (int r = resunit_to_code(p.get_string())) { + ok = TIFFSetField(m_tif, TIFFTAG_RESOLUTIONUNIT, r); + } + handled = true; + } else if (tag == EXIF_EXIFVERSION || tag == EXIF_FLASHPIXVERSION) { + if (p.type() == TypeString) { + // These tags are a 4-byte array of chars, but we + // allow users to set it as a string. Convert it if needed. + std::string version = p.get_string(); + if (version.size() >= 4) { + ok = TIFFSetField(m_tif, tag, version.c_str()); + } + handled = true; + } else if (p.type() == TypeInt) { + std::string s = Strutil::fmt::format("{:04}", p.get_int()); + if (s.size() == 4) + ok = TIFFSetField(m_tif, tag, s.c_str()); + handled = true; + } + } + // General cases... + else if (tifftype == TIFF_ASCII) { + ok = TIFFSetField(m_tif, tag, p.get_string().c_str()); + handled = true; + } else if (tifftype == TIFF_SHORT || tifftype == TIFF_SSHORT + || tifftype == TIFF_LONG || tifftype == TIFF_SLONG) { + if ((p.type() == TypeInt16 || p.type() == TypeInt32 + || p.type() == TypeUInt16 || p.type() == TypeUInt32) + && count == 1) { + // Passing our kinda-int as TIFF kinda-int + ok = TIFFSetField(m_tif, tag, p.get_int()); + handled = true; + } else if (p.type() == TypeString && count == 1) { + // Passing our string as TIFF kinda-int -- convert as long + // as the string looks like an int. + std::string s = p.get_string(); + if (Strutil::string_is_int(s)) { + int val = Strutil::stoi(s); + ok = TIFFSetField(m_tif, tag, val); + handled = true; + } + } } else if ((tifftype == TIFF_RATIONAL || tifftype == TIFF_SRATIONAL) - && p.type() == TypeDesc::DOUBLE) { - ok = TIFFSetField(m_tif, tag, *(double*)p.data()); + && (p.type() == TypeFloat || p.type() == TypeDesc::DOUBLE + || p.type() == TypeUInt16 || p.type() == TypeUInt32 + || p.type() == TypeInt16 || p.type() == TypeInt32 + || p.type() == TypeRational + || p.type() == TypeURational) + && count == 1) { + // If the tag is a rational, there are a number of types we + // can force into that form by converting to and then passing + // a float. + ok = TIFFSetField(m_tif, tag, p.get_float()); + handled = true; } + if (!handled) { +# if 0 + print("Unhandled EXIF {} ({}) / tag {} tifftype {} count {}\n", + p.name(), p.type(), tag, tifftype, count); +# endif + } + // NOTE: We are not handling arrays of values, just scalars. if (!ok) { - // std::cout << "Unhandled EXIF " << p.name() << " " << p.type() << "\n"; +# if 0 + print( + "Error handling EXIF {} ({}) / tag {} tifftype {} count {}\n", + p.name(), p.type(), tag, tifftype, count); +# endif } } } diff --git a/src/webp.imageio/webpinput.cpp b/src/webp.imageio/webpinput.cpp index 09cc08c4cd..bbe35d9822 100644 --- a/src/webp.imageio/webpinput.cpp +++ b/src/webp.imageio/webpinput.cpp @@ -214,8 +214,11 @@ WebpInput::open(const std::string& name, ImageSpec& spec, // Make space for the decoded image m_decoded_image.reset(new uint8_t[m_spec.image_bytes()]); - if (config.get_int_attribute("oiio:UnassociatedAlpha", 0) == 1) + if (config.get_int_attribute("oiio:UnassociatedAlpha", 0) == 1) { m_keep_unassociated_alpha = true; + if (m_spec.alpha_channel != -1) + m_spec.attribute("oiio:UnassociatedAlpha", 1); + } seek_subimage(0, 0); spec = m_spec; diff --git a/src/webp.imageio/webpoutput.cpp b/src/webp.imageio/webpoutput.cpp index 2c2acb85cc..784575963e 100644 --- a/src/webp.imageio/webpoutput.cpp +++ b/src/webp.imageio/webpoutput.cpp @@ -73,7 +73,7 @@ WebpImageWriter(const uint8_t* img_data, size_t data_size, bool WebpOutput::open(const std::string& name, const ImageSpec& spec, OpenMode mode) { - if (!check_open(mode, spec, { 0, 1 << 20, 0, 1 << 20, 0, 1, 0, 4 }, + if (!check_open(mode, spec, { 0, 16383, 0, 16383, 0, 1, 0, 4 }, uint64_t(OpenChecks::Disallow1or2Channel))) return false; diff --git a/src/zfile.imageio/zfile.cpp b/src/zfile.imageio/zfile.cpp index 65b3f4fc54..d2e326aa4a 100644 --- a/src/zfile.imageio/zfile.cpp +++ b/src/zfile.imageio/zfile.cpp @@ -92,16 +92,12 @@ class ZfileOutput final : public ImageOutput { bool close() override; bool write_scanline(int y, int z, TypeDesc format, const void* data, stride_t xstride) override; - bool write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, - stride_t zstride) override; private: std::string m_filename; ///< Stash the filename FILE* m_file; ///< Open image handle for not compressed gzFile m_gz; ///< Handle for compressed files std::vector m_scratch; - std::vector m_tilebuffer; bool opened() const { return m_file || m_gz; } @@ -112,7 +108,6 @@ class ZfileOutput final : public ImageOutput { m_gz = 0; m_filename.clear(); m_scratch.clear(); - m_tilebuffer.clear(); } }; @@ -306,11 +301,6 @@ ZfileOutput::open(const std::string& name, const ImageSpec& userspec, return false; } - // If user asked for tiles -- which this format doesn't support, emulate - // it by buffering the whole image.this form - if (m_spec.tile_width && m_spec.tile_height) - m_tilebuffer.resize(m_spec.image_bytes()); - return true; } @@ -324,15 +314,6 @@ ZfileOutput::close() return true; } - bool ok = true; - if (m_spec.tile_width && m_tilebuffer.size()) { - // We've been emulating tiles; now dump as scanlines. - ok &= write_scanlines(m_spec.y, m_spec.y + m_spec.height, 0, - m_spec.format, m_tilebuffer.data()); - m_tilebuffer.clear(); - m_tilebuffer.shrink_to_fit(); - } - if (m_gz) { gzclose(m_gz); m_gz = 0; @@ -343,7 +324,7 @@ ZfileOutput::close() } init(); // re-initialize - return ok; + return true; } @@ -382,19 +363,4 @@ ZfileOutput::write_scanline(int y, int /*z*/, TypeDesc format, const void* data, -bool -ZfileOutput::write_tile(int x, int y, int z, TypeDesc format, const void* data, - stride_t xstride, stride_t ystride, stride_t zstride) -{ - if (!opened()) { - errorfmt("File not open"); - return false; - } - // Emulate tiles by buffering the whole image - OIIO_ASSERT(m_tilebuffer.data()); - return copy_tile_to_image_buffer(x, y, z, format, data, xstride, ystride, - zstride, m_tilebuffer.data()); -} - - OIIO_PLUGIN_NAMESPACE_END diff --git a/testsuite/bmp/ref/out.txt b/testsuite/bmp/ref/out.txt index 13fc651063..2f7323ea0c 100644 --- a/testsuite/bmp/ref/out.txt +++ b/testsuite/bmp/ref/out.txt @@ -298,3 +298,6 @@ oiiotool ERROR: read : "src/bad-y.bmp": BMP might be corrupted, it is referencin BMP error reading rle-compressed image Full command line was: > oiiotool --info -v -a --hash src/bad-y.bmp +oiiotool ERROR: read : "src/palette32bit-corrupt.bmp": BMP error: bad BPP (32) for palette image -- presumed corrupt file +Full command line was: +> oiiotool --info -v -a --hash src/palette32bit-corrupt.bmp diff --git a/testsuite/bmp/run.py b/testsuite/bmp/run.py index 0e53937bdf..5f49b64682 100755 --- a/testsuite/bmp/run.py +++ b/testsuite/bmp/run.py @@ -33,3 +33,4 @@ # See if we handle these corrupt files with useful error messages command += info_command ("src/decodecolormap-corrupt.bmp") command += info_command ("src/bad-y.bmp") +command += info_command ("src/palette32bit-corrupt.bmp") diff --git a/testsuite/bmp/src/palette32bit-corrupt.bmp b/testsuite/bmp/src/palette32bit-corrupt.bmp new file mode 100644 index 0000000000..1f140906b4 Binary files /dev/null and b/testsuite/bmp/src/palette32bit-corrupt.bmp differ diff --git a/testsuite/heif/ref/out-libheif1.12-orient.txt b/testsuite/heif/ref/out-libheif1.12-orient.txt index 875e3ac54e..2765da7eda 100644 --- a/testsuite/heif/ref/out-libheif1.12-orient.txt +++ b/testsuite/heif/ref/out-libheif1.12-orient.txt @@ -182,3 +182,17 @@ Reading ../oiio-images/heif/sewing-threads.heic GPS:Longitude: 1, 49, 34.0187 GPS:LongitudeRef: "E" oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-8bit.avif +mono-8bit.avif : 64 x 64, 3 channel, uint10 heif + SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE + channel list: R, G, B + CICP: 2, 2, 6, 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-10bit.avif +mono-10bit.avif : 64 x 64, 3 channel, uint10 heif + SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE + channel list: R, G, B + CICP: 2, 2, 6, 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" diff --git a/testsuite/heif/ref/out-libheif1.21-with-av1.txt b/testsuite/heif/ref/out-libheif1.21-with-av1.txt new file mode 100644 index 0000000000..7843a32b31 --- /dev/null +++ b/testsuite/heif/ref/out-libheif1.21-with-av1.txt @@ -0,0 +1,176 @@ +Reading ref/IMG_7702_small.heic +ref/IMG_7702_small.heic : 512 x 300, 3 channel, uint8 heif + SHA-1: 2380C124F8338910013FEA75C9C64C23567A3156 + channel list: R, G, B + DateTime: "2019:01:21 16:10:54" + ExposureTime: 0.030303 + FNumber: 1.8 + Make: "Apple" + Model: "iPhone 7" + Orientation: 1 (normal) + ResolutionUnit: 2 (inches) + Software: "12.1.2" + XResolution: 72 + YResolution: 72 + Exif:ApertureValue: 1.69599 (f/1.8) + Exif:BrightnessValue: 3.99501 + Exif:ColorSpace: 65535 + Exif:DateTimeDigitized: "2019:01:21 16:10:54" + Exif:DateTimeOriginal: "2019:01:21 16:10:54" + Exif:ExifVersion: "0221" + Exif:ExposureBiasValue: 0 + Exif:ExposureMode: 0 (auto) + Exif:ExposureProgram: 2 (normal program) + Exif:Flash: 24 (no flash, auto flash) + Exif:FlashPixVersion: "0100" + Exif:FocalLength: 3.99 (3.99 mm) + Exif:FocalLengthIn35mmFilm: 28 + Exif:LensMake: "Apple" + Exif:LensModel: "iPhone 7 back camera 3.99mm f/1.8" + Exif:LensSpecification: 3.99, 3.99, 1.8, 1.8 + Exif:MeteringMode: 5 (pattern) + Exif:PhotographicSensitivity: 20 + Exif:PixelXDimension: 4032 + Exif:PixelYDimension: 3024 + Exif:SceneCaptureType: 0 (standard) + Exif:SensingMethod: 2 (1-chip color area) + Exif:ShutterSpeedValue: 5.03599 (1/32 s) + Exif:SubsecTimeDigitized: "006" + Exif:SubsecTimeOriginal: "006" + Exif:WhiteBalance: 0 (auto) + oiio:ColorSpace: "srgb_rec709_scene" +Reading ref/Chimera-AV1-8bit-162.avif +ref/Chimera-AV1-8bit-162.avif : 480 x 270, 3 channel, uint8 heif + SHA-1: F8FDAF1BD56A21E3AF99CF8EE7FA45434D2826C7 + channel list: R, G, B + oiio:ColorSpace: "srgb_rec709_scene" +Reading ref/test-10bit.avif +ref/test-10bit.avif : 16 x 16, 4 channel, uint10 heif + SHA-1: A217653C4E10FEBF080E26F9FC78F572184B1FDA + channel list: R, G, B, A + Software: "OpenImageIO 3.2.0.0dev : B4BD496D92983E84F1FD621682CAB821C1E2126C" + Exif:ExifVersion: "0230" + Exif:FlashPixVersion: "0100" + Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png" + heif:UnassociatedAlpha: 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" +Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic +../oiio-images/heif/greyhounds-looking-for-a-table.heic : 3024 x 4032, 3 channel, uint8 heif + SHA-1: 8064B23A1A995B0D6525AFB5248EEC6C730BBB6C + channel list: R, G, B + DateTime: "2023:09:28 09:44:03" + ExposureTime: 0.0135135 + FNumber: 2.4 + Make: "Apple" + Model: "iPhone 12 Pro" + Orientation: 1 (normal) + ResolutionUnit: 2 (inches) + Software: "16.7" + XResolution: 72 + YResolution: 72 + Exif:ApertureValue: 2.52607 (f/2.4) + Exif:BrightnessValue: 2.7506 + Exif:ColorSpace: 65535 + Exif:CompositeImage: 2 + Exif:DateTimeDigitized: "2023:09:28 09:44:03" + Exif:DateTimeOriginal: "2023:09:28 09:44:03" + Exif:DigitalZoomRatio: 1.3057 + Exif:ExifVersion: "0232" + Exif:ExposureBiasValue: 0 + Exif:ExposureMode: 0 (auto) + Exif:ExposureProgram: 2 (normal program) + Exif:Flash: 16 (no flash, flash suppression) + Exif:FocalLength: 1.54 (1.54 mm) + Exif:FocalLengthIn35mmFilm: 17 + Exif:LensMake: "Apple" + Exif:LensModel: "iPhone 12 Pro back triple camera 1.54mm f/2.4" + Exif:LensSpecification: 1.54, 6, 1.6, 2.4 + Exif:MeteringMode: 5 (pattern) + Exif:OffsetTime: "+02:00" + Exif:OffsetTimeDigitized: "+02:00" + Exif:OffsetTimeOriginal: "+02:00" + Exif:PhotographicSensitivity: 320 + Exif:PixelXDimension: 4032 + Exif:PixelYDimension: 3024 + Exif:SensingMethod: 2 (1-chip color area) + Exif:ShutterSpeedValue: 6.20983 (1/74 s) + Exif:SubsecTimeDigitized: "886" + Exif:SubsecTimeOriginal: "886" + Exif:WhiteBalance: 0 (auto) + GPS:Altitude: 3.24105 (3.24105 m) + GPS:AltitudeRef: 0 (above sea level) + GPS:DateStamp: "2023:09:28" + GPS:DestBearing: 90.2729 + GPS:DestBearingRef: "T" (true north) + GPS:HPositioningError: 5.1893 + GPS:ImgDirection: 90.2729 + GPS:ImgDirectionRef: "T" (true north) + GPS:Latitude: 41, 50, 58.43 + GPS:LatitudeRef: "N" + GPS:Longitude: 3, 7, 31.98 + GPS:LongitudeRef: "E" + GPS:Speed: 0.171966 + GPS:SpeedRef: "K" (km/hour) + oiio:ColorSpace: "srgb_rec709_scene" + oiio:OriginalOrientation: 6 +Reading ../oiio-images/heif/sewing-threads.heic +../oiio-images/heif/sewing-threads.heic : 4000 x 3000, 3 channel, uint8 heif + SHA-1: 44551A0A8AADD2C71B504681F2BAE3F7863EF9B9 + channel list: R, G, B + DateTime: "2023:12:12 18:39:16" + ExposureTime: 0.04 + FNumber: 1.8 + Make: "samsung" + Model: "SM-A326B" + Orientation: 1 (normal) + ResolutionUnit: 2 (inches) + Software: "A326BXXS8CWK2" + XResolution: 72 + YResolution: 72 + Exif:ApertureValue: 1.69 (f/1.8) + Exif:BrightnessValue: 1.19 + Exif:ColorSpace: 1 + Exif:DateTimeDigitized: "2023:12:12 18:39:16" + Exif:DateTimeOriginal: "2023:12:12 18:39:16" + Exif:DigitalZoomRatio: 1 + Exif:ExifVersion: "0220" + Exif:ExposureBiasValue: 0 + Exif:ExposureMode: 0 (auto) + Exif:ExposureProgram: 2 (normal program) + Exif:Flash: 0 (no flash) + Exif:FocalLength: 4.6 (4.6 mm) + Exif:FocalLengthIn35mmFilm: 25 + Exif:MaxApertureValue: 1.69 (f/1.8) + Exif:MeteringMode: 2 (center-weighted average) + Exif:OffsetTime: "+01:00" + Exif:OffsetTimeOriginal: "+01:00" + Exif:PhotographicSensitivity: 500 + Exif:PixelXDimension: 4000 + Exif:PixelYDimension: 3000 + Exif:SceneCaptureType: 0 (standard) + Exif:ShutterSpeedValue: 0.04 (1/1 s) + Exif:SubsecTime: "576" + Exif:SubsecTimeDigitized: "576" + Exif:SubsecTimeOriginal: "576" + Exif:WhiteBalance: 0 (auto) + Exif:YCbCrPositioning: 1 + GPS:Altitude: 292 (292 m) + GPS:AltitudeRef: 0 (above sea level) + GPS:Latitude: 41, 43, 33.821 + GPS:LatitudeRef: "N" + GPS:Longitude: 1, 49, 34.0187 + GPS:LongitudeRef: "E" + oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-8bit.avif +mono-8bit.avif : 64 x 64, 1 channel, uint10 heif + SHA-1: 09BE4368A01BE26600CA54D797477ABC5A37CB7B + channel list: Y + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-10bit.avif +mono-10bit.avif : 64 x 64, 1 channel, uint10 heif + SHA-1: 09BE4368A01BE26600CA54D797477ABC5A37CB7B + channel list: Y + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" diff --git a/testsuite/heif/ref/out-libheif1.21.txt b/testsuite/heif/ref/out-libheif1.21.txt new file mode 100644 index 0000000000..5a89c9e2ae --- /dev/null +++ b/testsuite/heif/ref/out-libheif1.21.txt @@ -0,0 +1,196 @@ +Reading ref/IMG_7702_small.heic +ref/IMG_7702_small.heic : 512 x 300, 3 channel, uint8 heif + SHA-1: 2380C124F8338910013FEA75C9C64C23567A3156 + channel list: R, G, B + DateTime: "2019:01:21 16:10:54" + ExposureTime: 0.030303 + FNumber: 1.8 + Make: "Apple" + Model: "iPhone 7" + Orientation: 1 (normal) + ResolutionUnit: 2 (inches) + Software: "12.1.2" + XResolution: 72 + YResolution: 72 + Exif:ApertureValue: 1.69599 (f/1.8) + Exif:BrightnessValue: 3.99501 + Exif:ColorSpace: 65535 + Exif:DateTimeDigitized: "2019:01:21 16:10:54" + Exif:DateTimeOriginal: "2019:01:21 16:10:54" + Exif:ExifVersion: "0221" + Exif:ExposureBiasValue: 0 + Exif:ExposureMode: 0 (auto) + Exif:ExposureProgram: 2 (normal program) + Exif:Flash: 24 (no flash, auto flash) + Exif:FlashPixVersion: "0100" + Exif:FocalLength: 3.99 (3.99 mm) + Exif:FocalLengthIn35mmFilm: 28 + Exif:LensMake: "Apple" + Exif:LensModel: "iPhone 7 back camera 3.99mm f/1.8" + Exif:LensSpecification: 3.99, 3.99, 1.8, 1.8 + Exif:MeteringMode: 5 (pattern) + Exif:PhotographicSensitivity: 20 + Exif:PixelXDimension: 4032 + Exif:PixelYDimension: 3024 + Exif:SceneCaptureType: 0 (standard) + Exif:SensingMethod: 2 (1-chip color area) + Exif:ShutterSpeedValue: 5.03599 (1/32 s) + Exif:SubsecTimeDigitized: "006" + Exif:SubsecTimeOriginal: "006" + Exif:WhiteBalance: 0 (auto) + oiio:ColorSpace: "srgb_rec709_scene" +Reading ref/Chimera-AV1-8bit-162.avif +ref/Chimera-AV1-8bit-162.avif : 480 x 270, 3 channel, uint8 heif + SHA-1: F8FDAF1BD56A21E3AF99CF8EE7FA45434D2826C7 + channel list: R, G, B + oiio:ColorSpace: "srgb_rec709_scene" +Reading ref/test-10bit.avif +ref/test-10bit.avif : 16 x 16, 4 channel, uint10 heif + SHA-1: A217653C4E10FEBF080E26F9FC78F572184B1FDA + channel list: R, G, B, A + Software: "OpenImageIO 3.2.0.0dev : B4BD496D92983E84F1FD621682CAB821C1E2126C" + Exif:ExifVersion: "0230" + Exif:FlashPixVersion: "0100" + Exif:ImageHistory: "oiiotool --pattern fill:topleft=1,0,0,1:topright=0,1,0,1:bottomleft=0,0,1,1:bottomright=1,1,1,1 16x16 4 -d uint16 -o test16.png" + heif:UnassociatedAlpha: 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" +Reading cicp_pq.avif +cicp_pq.avif : 16 x 16, 4 channel, uint10 heif + SHA-1: 0F3CAB52D479BC23E9C981DBADDFEF1F792E5540 + channel list: R, G, B, A + CICP: 9, 16, 9, 1 + Exif:ExifVersion: "0230" + Exif:FlashPixVersion: "0100" + heif:UnassociatedAlpha: 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "pq_rec2020_display" +Reading colorspace_hlg.avif +colorspace_hlg.avif : 16 x 16, 4 channel, uint10 heif + SHA-1: 0F3CAB52D479BC23E9C981DBADDFEF1F792E5540 + channel list: R, G, B, A + CICP: 9, 18, 9, 1 + Exif:ExifVersion: "0230" + Exif:FlashPixVersion: "0100" + heif:UnassociatedAlpha: 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "hlg_rec2020_display" +Reading ../oiio-images/heif/greyhounds-looking-for-a-table.heic +../oiio-images/heif/greyhounds-looking-for-a-table.heic : 3024 x 4032, 3 channel, uint8 heif + SHA-1: 8064B23A1A995B0D6525AFB5248EEC6C730BBB6C + channel list: R, G, B + DateTime: "2023:09:28 09:44:03" + ExposureTime: 0.0135135 + FNumber: 2.4 + Make: "Apple" + Model: "iPhone 12 Pro" + Orientation: 1 (normal) + ResolutionUnit: 2 (inches) + Software: "16.7" + XResolution: 72 + YResolution: 72 + Exif:ApertureValue: 2.52607 (f/2.4) + Exif:BrightnessValue: 2.7506 + Exif:ColorSpace: 65535 + Exif:CompositeImage: 2 + Exif:DateTimeDigitized: "2023:09:28 09:44:03" + Exif:DateTimeOriginal: "2023:09:28 09:44:03" + Exif:DigitalZoomRatio: 1.3057 + Exif:ExifVersion: "0232" + Exif:ExposureBiasValue: 0 + Exif:ExposureMode: 0 (auto) + Exif:ExposureProgram: 2 (normal program) + Exif:Flash: 16 (no flash, flash suppression) + Exif:FocalLength: 1.54 (1.54 mm) + Exif:FocalLengthIn35mmFilm: 17 + Exif:LensMake: "Apple" + Exif:LensModel: "iPhone 12 Pro back triple camera 1.54mm f/2.4" + Exif:LensSpecification: 1.54, 6, 1.6, 2.4 + Exif:MeteringMode: 5 (pattern) + Exif:OffsetTime: "+02:00" + Exif:OffsetTimeDigitized: "+02:00" + Exif:OffsetTimeOriginal: "+02:00" + Exif:PhotographicSensitivity: 320 + Exif:PixelXDimension: 4032 + Exif:PixelYDimension: 3024 + Exif:SensingMethod: 2 (1-chip color area) + Exif:ShutterSpeedValue: 6.20983 (1/74 s) + Exif:SubsecTimeDigitized: "886" + Exif:SubsecTimeOriginal: "886" + Exif:WhiteBalance: 0 (auto) + GPS:Altitude: 3.24105 (3.24105 m) + GPS:AltitudeRef: 0 (above sea level) + GPS:DateStamp: "2023:09:28" + GPS:DestBearing: 90.2729 + GPS:DestBearingRef: "T" (true north) + GPS:HPositioningError: 5.1893 + GPS:ImgDirection: 90.2729 + GPS:ImgDirectionRef: "T" (true north) + GPS:Latitude: 41, 50, 58.43 + GPS:LatitudeRef: "N" + GPS:Longitude: 3, 7, 31.98 + GPS:LongitudeRef: "E" + GPS:Speed: 0.171966 + GPS:SpeedRef: "K" (km/hour) + oiio:ColorSpace: "srgb_rec709_scene" + oiio:OriginalOrientation: 6 +Reading ../oiio-images/heif/sewing-threads.heic +../oiio-images/heif/sewing-threads.heic : 4000 x 3000, 3 channel, uint8 heif + SHA-1: 44551A0A8AADD2C71B504681F2BAE3F7863EF9B9 + channel list: R, G, B + DateTime: "2023:12:12 18:39:16" + ExposureTime: 0.04 + FNumber: 1.8 + Make: "samsung" + Model: "SM-A326B" + Orientation: 1 (normal) + ResolutionUnit: 2 (inches) + Software: "A326BXXS8CWK2" + XResolution: 72 + YResolution: 72 + Exif:ApertureValue: 1.69 (f/1.8) + Exif:BrightnessValue: 1.19 + Exif:ColorSpace: 1 + Exif:DateTimeDigitized: "2023:12:12 18:39:16" + Exif:DateTimeOriginal: "2023:12:12 18:39:16" + Exif:DigitalZoomRatio: 1 + Exif:ExifVersion: "0220" + Exif:ExposureBiasValue: 0 + Exif:ExposureMode: 0 (auto) + Exif:ExposureProgram: 2 (normal program) + Exif:Flash: 0 (no flash) + Exif:FocalLength: 4.6 (4.6 mm) + Exif:FocalLengthIn35mmFilm: 25 + Exif:MaxApertureValue: 1.69 (f/1.8) + Exif:MeteringMode: 2 (center-weighted average) + Exif:OffsetTime: "+01:00" + Exif:OffsetTimeOriginal: "+01:00" + Exif:PhotographicSensitivity: 500 + Exif:PixelXDimension: 4000 + Exif:PixelYDimension: 3000 + Exif:SceneCaptureType: 0 (standard) + Exif:ShutterSpeedValue: 0.04 (1/1 s) + Exif:SubsecTime: "576" + Exif:SubsecTimeDigitized: "576" + Exif:SubsecTimeOriginal: "576" + Exif:WhiteBalance: 0 (auto) + Exif:YCbCrPositioning: 1 + GPS:Altitude: 292 (292 m) + GPS:AltitudeRef: 0 (above sea level) + GPS:Latitude: 41, 43, 33.821 + GPS:LatitudeRef: "N" + GPS:Longitude: 1, 49, 34.0187 + GPS:LongitudeRef: "E" + oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-8bit.avif +mono-8bit.avif : 64 x 64, 1 channel, uint10 heif + SHA-1: 09BE4368A01BE26600CA54D797477ABC5A37CB7B + channel list: Y + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-10bit.avif +mono-10bit.avif : 64 x 64, 1 channel, uint10 heif + SHA-1: 09BE4368A01BE26600CA54D797477ABC5A37CB7B + channel list: Y + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" diff --git a/testsuite/heif/ref/out-libheif1.4.txt b/testsuite/heif/ref/out-libheif1.4.txt index 9d8304f14a..457e6045f2 100644 --- a/testsuite/heif/ref/out-libheif1.4.txt +++ b/testsuite/heif/ref/out-libheif1.4.txt @@ -182,3 +182,17 @@ Reading ../oiio-images/heif/sewing-threads.heic GPS:Longitude: 1, 49, 34.0187 GPS:LongitudeRef: "E" oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-8bit.avif +mono-8bit.avif : 64 x 64, 3 channel, uint10 heif + SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE + channel list: R, G, B + CICP: 2, 2, 6, 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-10bit.avif +mono-10bit.avif : 64 x 64, 3 channel, uint10 heif + SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE + channel list: R, G, B + CICP: 2, 2, 6, 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" diff --git a/testsuite/heif/ref/out-libheif1.5.txt b/testsuite/heif/ref/out-libheif1.5.txt index 9dfd4ff23d..b3dbaf12a3 100644 --- a/testsuite/heif/ref/out-libheif1.5.txt +++ b/testsuite/heif/ref/out-libheif1.5.txt @@ -182,3 +182,17 @@ Reading ../oiio-images/heif/sewing-threads.heic GPS:Longitude: 1, 49, 34.0187 GPS:LongitudeRef: "E" oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-8bit.avif +mono-8bit.avif : 64 x 64, 3 channel, uint10 heif + SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE + channel list: R, G, B + CICP: 2, 2, 6, 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-10bit.avif +mono-10bit.avif : 64 x 64, 3 channel, uint10 heif + SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE + channel list: R, G, B + CICP: 2, 2, 6, 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" diff --git a/testsuite/heif/ref/out-libheif1.9-alt2.txt b/testsuite/heif/ref/out-libheif1.9-alt2.txt index f6448d4836..a36c6b8a63 100644 --- a/testsuite/heif/ref/out-libheif1.9-alt2.txt +++ b/testsuite/heif/ref/out-libheif1.9-alt2.txt @@ -146,3 +146,17 @@ Reading ../oiio-images/heif/sewing-threads.heic GPS:Longitude: 1, 49, 34.0187 GPS:LongitudeRef: "E" oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-8bit.avif +mono-8bit.avif : 64 x 64, 3 channel, uint10 heif + SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE + channel list: R, G, B + CICP: 2, 2, 6, 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-10bit.avif +mono-10bit.avif : 64 x 64, 3 channel, uint10 heif + SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE + channel list: R, G, B + CICP: 2, 2, 6, 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" diff --git a/testsuite/heif/ref/out-libheif1.9-with-av1-alt2.txt b/testsuite/heif/ref/out-libheif1.9-with-av1-alt2.txt index c938a6fe73..36ca82c6f4 100644 --- a/testsuite/heif/ref/out-libheif1.9-with-av1-alt2.txt +++ b/testsuite/heif/ref/out-libheif1.9-with-av1-alt2.txt @@ -182,3 +182,17 @@ Reading ../oiio-images/heif/sewing-threads.heic GPS:Longitude: 1, 49, 34.0187 GPS:LongitudeRef: "E" oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-8bit.avif +mono-8bit.avif : 64 x 64, 3 channel, uint10 heif + SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE + channel list: R, G, B + CICP: 2, 2, 6, 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-10bit.avif +mono-10bit.avif : 64 x 64, 3 channel, uint10 heif + SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE + channel list: R, G, B + CICP: 2, 2, 6, 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" diff --git a/testsuite/heif/ref/out-libheif1.9-with-av1.txt b/testsuite/heif/ref/out-libheif1.9-with-av1.txt index f6d7ca55a5..bf56e7c97b 100644 --- a/testsuite/heif/ref/out-libheif1.9-with-av1.txt +++ b/testsuite/heif/ref/out-libheif1.9-with-av1.txt @@ -182,3 +182,17 @@ Reading ../oiio-images/heif/sewing-threads.heic GPS:Longitude: 1, 49, 34.0187 GPS:LongitudeRef: "E" oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-8bit.avif +mono-8bit.avif : 64 x 64, 3 channel, uint10 heif + SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE + channel list: R, G, B + CICP: 2, 2, 6, 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-10bit.avif +mono-10bit.avif : 64 x 64, 3 channel, uint10 heif + SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE + channel list: R, G, B + CICP: 2, 2, 6, 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" diff --git a/testsuite/heif/ref/out-libheif1.9.txt b/testsuite/heif/ref/out-libheif1.9.txt index 2778c33493..0772246c63 100644 --- a/testsuite/heif/ref/out-libheif1.9.txt +++ b/testsuite/heif/ref/out-libheif1.9.txt @@ -146,3 +146,17 @@ Reading ../oiio-images/heif/sewing-threads.heic GPS:Longitude: 1, 49, 34.0187 GPS:LongitudeRef: "E" oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-8bit.avif +mono-8bit.avif : 64 x 64, 3 channel, uint10 heif + SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE + channel list: R, G, B + CICP: 2, 2, 6, 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" +Reading mono-10bit.avif +mono-10bit.avif : 64 x 64, 3 channel, uint10 heif + SHA-1: 4E361351029D39379C73580BCD4C9859E4B73ADE + channel list: R, G, B + CICP: 2, 2, 6, 1 + oiio:BitsPerSample: 10 + oiio:ColorSpace: "srgb_rec709_scene" diff --git a/testsuite/heif/run.py b/testsuite/heif/run.py index 0a3250e374..5d5945d870 100755 --- a/testsuite/heif/run.py +++ b/testsuite/heif/run.py @@ -22,5 +22,11 @@ for f in files: command = command + info_command (os.path.join(OIIO_TESTSUITE_IMAGEDIR, f)) +command += oiiotool("--pattern checker:color1=1:color2=0 64x64 1 -o mono-8bit.avif") +command += info_command("mono-8bit.avif", safematch=True) + +command += oiiotool("--pattern checker:color1=1:color2=0 64x64 1 -d uint10 -o mono-10bit.avif") +command += info_command("mono-10bit.avif", safematch=True) + # avif conversion is expected to fail if libheif is built without AV1 support failureok = 1 diff --git a/testsuite/jpeg/ref/out-jpeg9.4.txt b/testsuite/jpeg/ref/out-jpeg9.4.txt new file mode 100644 index 0000000000..bc8035159e --- /dev/null +++ b/testsuite/jpeg/ref/out-jpeg9.4.txt @@ -0,0 +1,9 @@ +Reading src/YCbCrK.jpg +src/YCbCrK.jpg : 52 x 52, 3 channel, uint8 jpeg + SHA-1: B54FAE77E27EFCEACF27BA796A48DCE6DF262F26 + channel list: R, G, B + jpeg:ColorSpace: "YCbCrK" + jpeg:subsampling: "4:4:4" + oiio:ColorSpace: "srgb_rec709_scene" +Comparing "rgb-from-YCbCrK.tif" and "ref/rgb-from-YCbCrK.tif" +PASS diff --git a/testsuite/jxl/ref/out.txt b/testsuite/jxl/ref/out.txt index 83b97fbba6..0ed56fe485 100644 --- a/testsuite/jxl/ref/out.txt +++ b/testsuite/jxl/ref/out.txt @@ -42,3 +42,48 @@ tahoe-cicp-pq.jxl : 128 x 96, 3 channel, uint8 jpegxl ICCProfile:profile_version: "4.4.0" ICCProfile:rendering_intent: "Perceptual" oiio:ColorSpace: "pq_rec2020_display" +Reading tahoe-cicp-dcip3.jxl +tahoe-cicp-dcip3.jxl : 128 x 96, 3 channel, uint8 jpegxl + SHA-1: 069F1A3E5567349C2D34E535B29913029EF1B09C + channel list: R, G, B + CICP: 11, 17, 0, 1 + ICCProfile: 0, 0, 2, 24, 106, 120, 108, 32, 4, 64, 0, 0, 109, 110, 116, 114, ... [536 x uint8] + ICCProfile:attributes: "Reflective, Glossy, Positive, Color" + ICCProfile:cmm_type: 1786276896 + ICCProfile:color_space: "RGB" + ICCProfile:copyright: "CC0" + ICCProfile:creation_date: "2019:12:01 00:00:00" + ICCProfile:creator_signature: "6a786c20" + ICCProfile:device_class: "Display device profile" + ICCProfile:flags: "Not Embedded, Independent" + ICCProfile:manufacturer: "0" + ICCProfile:model: "0" + ICCProfile:platform_signature: "Apple Computer, Inc." + ICCProfile:profile_connection_space: "XYZ" + ICCProfile:profile_description: "RGB_DCI_DCI_Per_DCI" + ICCProfile:profile_size: 536 + ICCProfile:profile_version: "4.4.0" + ICCProfile:rendering_intent: "Perceptual" +Reading tahoe-cicp-displayp3.jxl +tahoe-cicp-displayp3.jxl : 128 x 96, 3 channel, uint8 jpegxl + SHA-1: 069F1A3E5567349C2D34E535B29913029EF1B09C + channel list: R, G, B + CICP: 12, 13, 0, 1 + ICCProfile: 0, 0, 2, 4, 106, 120, 108, 32, 4, 64, 0, 0, 109, 110, 116, 114, ... [516 x uint8] + ICCProfile:attributes: "Reflective, Glossy, Positive, Color" + ICCProfile:cmm_type: 1786276896 + ICCProfile:color_space: "RGB" + ICCProfile:copyright: "CC0" + ICCProfile:creation_date: "2019:12:01 00:00:00" + ICCProfile:creator_signature: "6a786c20" + ICCProfile:device_class: "Display device profile" + ICCProfile:flags: "Not Embedded, Independent" + ICCProfile:manufacturer: "0" + ICCProfile:model: "0" + ICCProfile:platform_signature: "Apple Computer, Inc." + ICCProfile:profile_connection_space: "XYZ" + ICCProfile:profile_description: "DisplayP3" + ICCProfile:profile_size: 516 + ICCProfile:profile_version: "4.4.0" + ICCProfile:rendering_intent: "Perceptual" + oiio:ColorSpace: "srgb_p3d65_scene" diff --git a/testsuite/jxl/run.py b/testsuite/jxl/run.py index 78b7a19eba..a3299e02a9 100755 --- a/testsuite/jxl/run.py +++ b/testsuite/jxl/run.py @@ -13,6 +13,12 @@ command += oiiotool ("../common/tahoe-tiny.tif --cicp \"9,16,9,1\" -o tahoe-cicp-pq.jxl") command += info_command ("tahoe-cicp-pq.jxl", safematch=True) +command += oiiotool ("../common/tahoe-tiny.tif --cicp \"11,17,0,1\" -o tahoe-cicp-dcip3.jxl") +command += info_command ("tahoe-cicp-dcip3.jxl", safematch=True) + +command += oiiotool ("../common/tahoe-tiny.tif --cicp \"12,13,0,1\" -o tahoe-cicp-displayp3.jxl") +command += info_command ("tahoe-cicp-displayp3.jxl", safematch=True) + outputs = [ "test-jxl.icc", "out.txt" diff --git a/testsuite/oiiotool-control/ref/out.txt b/testsuite/oiiotool-control/ref/out.txt index 999e3d598b..c8fed296cf 100644 --- a/testsuite/oiiotool-control/ref/out.txt +++ b/testsuite/oiiotool-control/ref/out.txt @@ -1,5 +1,19 @@ -Stack holds [0] = d.tif, [1] = c.tif, [2] = b.tif +Stack holds [0] = d.tif, [1] = c.tif, [2] = b.tif , [3] = a.tif TOP = d.tif, BOTTOM = a.tif +Stack holds [0] = b.tif, [1] = a.tif +TOP = b.tif, BOTTOM = a.tif +Stack holds [0] = a.tif +TOP = a.tif, BOTTOM = a.tif +Stack is empty +oiiotool ERROR: expression : not a valid image at char 4 of 'TOP.filename' +Full command line was: +> oiiotool --echo "Stack is empty" --echo "TOP = {TOP.filename}" +TOP = TOP.filename +Stack is empty +oiiotool ERROR: expression : not a valid image at char 7 of 'BOTTOM.filename' +Full command line was: +> oiiotool --echo "Stack is empty" --echo "BOTTOM = {BOTTOM.filename}" +BOTTOM = BOTTOM.filename Stack bottom to top: a.tif b.tif diff --git a/testsuite/oiiotool-control/run.py b/testsuite/oiiotool-control/run.py index 51c9a03f1b..c165df5fed 100755 --- a/testsuite/oiiotool-control/run.py +++ b/testsuite/oiiotool-control/run.py @@ -23,9 +23,26 @@ # Test TOP, BOTTOM, IMG[] # TOP should be c.tif, BOTTOM should be a.tif command += oiiotool ("a.tif b.tif c.tif d.tif " + - "--echo \"Stack holds [0] = {IMG[0].filename}, [1] = {IMG[1].filename}, [2] = {IMG[2].filename}\" " + + "--echo \"Stack holds [0] = {IMG[0].filename}, [1] = {IMG[1].filename}, [2] = {IMG[2].filename} , [3] = {IMG[3].filename}\" " + + "--echo \"TOP = {TOP.filename}, BOTTOM = {BOTTOM.filename}\" " + ) +# Regression test (Issue #5044): make sure BOTTOM works correctly for 0-2 images +command += oiiotool ("a.tif b.tif " + + "--echo \"Stack holds [0] = {IMG[0].filename}, [1] = {IMG[1].filename}\" " + + "--echo \"TOP = {TOP.filename}, BOTTOM = {BOTTOM.filename}\" " + ) +command += oiiotool ("a.tif " + + "--echo \"Stack holds [0] = {IMG[0].filename}\" " + "--echo \"TOP = {TOP.filename}, BOTTOM = {BOTTOM.filename}\" " ) +# Empty -- should get an error about TOP and BOTTOM not being available +command += oiiotool ("--echo \"Stack is empty\" " + + "--echo \"TOP = {TOP.filename}\" " + ) +command += oiiotool ("--echo \"Stack is empty\" " + + "--echo \"BOTTOM = {BOTTOM.filename}\" " + ) + # Test --pop, --popbottom, --stackreverse, --stackclear, --stackextract command += oiiotool ( "a.tif b.tif c.tif d.tif " diff --git a/testsuite/runtest.py b/testsuite/runtest.py index 5efced462f..052b68434a 100755 --- a/testsuite/runtest.py +++ b/testsuite/runtest.py @@ -162,9 +162,7 @@ def newsymlink(src: str, dst: str): if os.getenv("Python_EXECUTABLE") : pythonbin = os.getenv("Python_EXECUTABLE") else : - pythonbin = 'python' - if os.getenv("PYTHON_VERSION") : - pythonbin += os.getenv("PYTHON_VERSION") + pythonbin = sys.executable #print ("pythonbin = ", pythonbin) diff --git a/testsuite/texture-crop/run.py b/testsuite/texture-crop/run.py index 9973dc6afc..5623052c2e 100755 --- a/testsuite/texture-crop/run.py +++ b/testsuite/texture-crop/run.py @@ -4,6 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO +hardfail = 0.02 command += oiiotool("../common/grid.tif --crop 512x512+200+100 -o grid-crop.tif") command += maketx_command ("grid-crop.tif", "grid-crop.tx") diff --git a/testsuite/texture-interp-bilinear/run.py b/testsuite/texture-interp-bilinear/run.py index 0617c8319f..ee9dbed8b1 100755 --- a/testsuite/texture-interp-bilinear/run.py +++ b/testsuite/texture-interp-bilinear/run.py @@ -4,6 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO +hardfail = 0.02 command = testtex_command ("../common/textures/grid.tx", extraargs = "-interpmode 1 -d uint8 -o out.tif") diff --git a/testsuite/texture-skinny/run.py b/testsuite/texture-skinny/run.py index 8d2f16c7d7..c61eb79d98 100755 --- a/testsuite/texture-skinny/run.py +++ b/testsuite/texture-skinny/run.py @@ -4,6 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO +hardfail = 0.013 command = testtex_command ("src/vertgrid.tx", " --scalest 4 1 ") outputs = [ "out.exr" ] diff --git a/testsuite/texture-uint8/run.py b/testsuite/texture-uint8/run.py index efe91e14c1..af786ad492 100755 --- a/testsuite/texture-uint8/run.py +++ b/testsuite/texture-uint8/run.py @@ -4,6 +4,7 @@ # SPDX-License-Identifier: Apache-2.0 # https://github.com/AcademySoftwareFoundation/OpenImageIO +hardfail = 0.021 command = testtex_command ("../common/textures/grid.tx") outputs = [ "out.exr" ] diff --git a/testsuite/tiff-depths/ref/out-icc.txt b/testsuite/tiff-depths/ref/out-icc.txt deleted file mode 100644 index ae0e53bea4..0000000000 --- a/testsuite/tiff-depths/ref/out-icc.txt +++ /dev/null @@ -1,876 +0,0 @@ -Reading ../oiio-images/libtiffpic/depth/flower-minisblack-02.tif -../oiio-images/libtiffpic/depth/flower-minisblack-02.tif : 73 x 43, 1 channel, uint2 tiff - SHA-1: F6BD9D10FB0DD8E9AC62DEBBB743A78FC48D3C9B - channel list: Y - compression: "none" - DocumentName: "flower-minisblack-02.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 2 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 1 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 431 -Comparing "../oiio-images/libtiffpic/depth/flower-minisblack-02.tif" and "flower-minisblack-02.tif" -PASS -flower-minisblack-02.tif : 73 x 43, 1 channel, uint2 tiff - SHA-1: F6BD9D10FB0DD8E9AC62DEBBB743A78FC48D3C9B -../oiio-images/libtiffpic/depth/flower-minisblack-02.tif : 73 x 43, 1 channel, uint2 tiff - SHA-1: F6BD9D10FB0DD8E9AC62DEBBB743A78FC48D3C9B -Reading ../oiio-images/libtiffpic/depth/flower-minisblack-04.tif -../oiio-images/libtiffpic/depth/flower-minisblack-04.tif : 73 x 43, 1 channel, uint4 tiff - SHA-1: 8C0CF14B3B585F4B1F249C681BEDEA4CB63E3EDD - channel list: Y - compression: "none" - DocumentName: "flower-minisblack-04.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 4 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 1 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 221 -Comparing "../oiio-images/libtiffpic/depth/flower-minisblack-04.tif" and "flower-minisblack-04.tif" -PASS -flower-minisblack-04.tif : 73 x 43, 1 channel, uint4 tiff - SHA-1: 8C0CF14B3B585F4B1F249C681BEDEA4CB63E3EDD -../oiio-images/libtiffpic/depth/flower-minisblack-04.tif : 73 x 43, 1 channel, uint4 tiff - SHA-1: 8C0CF14B3B585F4B1F249C681BEDEA4CB63E3EDD -Reading ../oiio-images/libtiffpic/depth/flower-minisblack-06.tif -../oiio-images/libtiffpic/depth/flower-minisblack-06.tif : 73 x 43, 1 channel, uint6 tiff - SHA-1: AE809BFEF36E3E0047343655231200A916D83492 - channel list: Y - compression: "none" - DocumentName: "flower-minisblack-06.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 6 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 1 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 148 -Comparing "../oiio-images/libtiffpic/depth/flower-minisblack-06.tif" and "flower-minisblack-06.tif" -PASS -flower-minisblack-06.tif : 73 x 43, 1 channel, uint6 tiff - SHA-1: AE809BFEF36E3E0047343655231200A916D83492 -../oiio-images/libtiffpic/depth/flower-minisblack-06.tif : 73 x 43, 1 channel, uint6 tiff - SHA-1: AE809BFEF36E3E0047343655231200A916D83492 -Reading ../oiio-images/libtiffpic/depth/flower-minisblack-08.tif -../oiio-images/libtiffpic/depth/flower-minisblack-08.tif : 73 x 43, 1 channel, uint8 tiff - SHA-1: 1A909C8E70CC479D8A35BAA9BFEDDCBF4BF46FDC - channel list: Y - compression: "none" - DocumentName: "flower-minisblack-08.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 8 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 1 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 112 -Comparing "../oiio-images/libtiffpic/depth/flower-minisblack-08.tif" and "flower-minisblack-08.tif" -PASS -flower-minisblack-08.tif : 73 x 43, 1 channel, uint8 tiff - SHA-1: 1A909C8E70CC479D8A35BAA9BFEDDCBF4BF46FDC -../oiio-images/libtiffpic/depth/flower-minisblack-08.tif : 73 x 43, 1 channel, uint8 tiff - SHA-1: 1A909C8E70CC479D8A35BAA9BFEDDCBF4BF46FDC -Reading ../oiio-images/libtiffpic/depth/flower-minisblack-10.tif -../oiio-images/libtiffpic/depth/flower-minisblack-10.tif : 73 x 43, 1 channel, uint10 tiff - SHA-1: E9240FEF19CC8EF5EBBF2EE4A10EDF25E51C67B4 - channel list: Y - compression: "none" - DocumentName: "flower-minisblack-10.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 10 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 1 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 89 -Comparing "../oiio-images/libtiffpic/depth/flower-minisblack-10.tif" and "flower-minisblack-10.tif" -PASS -flower-minisblack-10.tif : 73 x 43, 1 channel, uint10 tiff - SHA-1: E9240FEF19CC8EF5EBBF2EE4A10EDF25E51C67B4 -../oiio-images/libtiffpic/depth/flower-minisblack-10.tif : 73 x 43, 1 channel, uint10 tiff - SHA-1: E9240FEF19CC8EF5EBBF2EE4A10EDF25E51C67B4 -Reading ../oiio-images/libtiffpic/depth/flower-minisblack-12.tif -../oiio-images/libtiffpic/depth/flower-minisblack-12.tif : 73 x 43, 1 channel, uint12 tiff - SHA-1: AAE977957ED6AAC647967192A74E4AD55FF75811 - channel list: Y - compression: "none" - DocumentName: "flower-minisblack-12.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 12 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 1 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 74 -Comparing "../oiio-images/libtiffpic/depth/flower-minisblack-12.tif" and "flower-minisblack-12.tif" -PASS -flower-minisblack-12.tif : 73 x 43, 1 channel, uint12 tiff - SHA-1: AAE977957ED6AAC647967192A74E4AD55FF75811 -../oiio-images/libtiffpic/depth/flower-minisblack-12.tif : 73 x 43, 1 channel, uint12 tiff - SHA-1: AAE977957ED6AAC647967192A74E4AD55FF75811 -Reading ../oiio-images/libtiffpic/depth/flower-minisblack-14.tif -../oiio-images/libtiffpic/depth/flower-minisblack-14.tif : 73 x 43, 1 channel, uint14 tiff - SHA-1: C1B9CA21C227EF11626EF0C58BD49769EEF48363 - channel list: Y - compression: "none" - DocumentName: "flower-minisblack-14.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 14 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 1 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 64 -Comparing "../oiio-images/libtiffpic/depth/flower-minisblack-14.tif" and "flower-minisblack-14.tif" -PASS -flower-minisblack-14.tif : 73 x 43, 1 channel, uint14 tiff - SHA-1: C1B9CA21C227EF11626EF0C58BD49769EEF48363 -../oiio-images/libtiffpic/depth/flower-minisblack-14.tif : 73 x 43, 1 channel, uint14 tiff - SHA-1: C1B9CA21C227EF11626EF0C58BD49769EEF48363 -Reading ../oiio-images/libtiffpic/depth/flower-minisblack-16.tif -../oiio-images/libtiffpic/depth/flower-minisblack-16.tif : 73 x 43, 1 channel, uint16 tiff - SHA-1: 7EBB74E46C869CA0D6D091183732214B6A75173A - channel list: Y - compression: "none" - DocumentName: "flower-minisblack-16.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 16 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 1 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 56 -Comparing "../oiio-images/libtiffpic/depth/flower-minisblack-16.tif" and "flower-minisblack-16.tif" -PASS -flower-minisblack-16.tif : 73 x 43, 1 channel, uint16 tiff - SHA-1: 7EBB74E46C869CA0D6D091183732214B6A75173A -../oiio-images/libtiffpic/depth/flower-minisblack-16.tif : 73 x 43, 1 channel, uint16 tiff - SHA-1: 7EBB74E46C869CA0D6D091183732214B6A75173A -Reading ../oiio-images/libtiffpic/depth/flower-minisblack-24.tif -../oiio-images/libtiffpic/depth/flower-minisblack-24.tif : 73 x 43, 1 channel, uint24 tiff - SHA-1: BBFA6633ECF3FF686DB36F6DD00F8A359D2B1DAF - channel list: Y - compression: "none" - DocumentName: "flower-minisblack-24.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q8 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 24 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 1 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 37 -Comparing "../oiio-images/libtiffpic/depth/flower-minisblack-24.tif" and "flower-minisblack-24.tif" -PASS -flower-minisblack-24.tif : 73 x 43, 1 channel, uint24 tiff - SHA-1: BBFA6633ECF3FF686DB36F6DD00F8A359D2B1DAF -../oiio-images/libtiffpic/depth/flower-minisblack-24.tif : 73 x 43, 1 channel, uint24 tiff - SHA-1: BBFA6633ECF3FF686DB36F6DD00F8A359D2B1DAF -Reading ../oiio-images/libtiffpic/depth/flower-minisblack-32.tif -../oiio-images/libtiffpic/depth/flower-minisblack-32.tif : 73 x 43, 1 channel, uint tiff - SHA-1: C98FB1125C7210E380E3F86DFCAEFF49A16742E0 - channel list: Y - compression: "none" - DocumentName: "flower-minisblack-32.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 32 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 1 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 28 -Comparing "../oiio-images/libtiffpic/depth/flower-minisblack-32.tif" and "flower-minisblack-32.tif" -PASS -flower-minisblack-32.tif : 73 x 43, 1 channel, uint tiff - SHA-1: C98FB1125C7210E380E3F86DFCAEFF49A16742E0 -../oiio-images/libtiffpic/depth/flower-minisblack-32.tif : 73 x 43, 1 channel, uint tiff - SHA-1: C98FB1125C7210E380E3F86DFCAEFF49A16742E0 -Reading ../oiio-images/libtiffpic/depth/flower-palette-02.tif -../oiio-images/libtiffpic/depth/flower-palette-02.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: 52B3033465AA01129BAE149FF96CBB49877DAB7C - channel list: R, G, B - compression: "none" - DocumentName: "flower-palette-02.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 8 - tiff:BitsPerSample: 2 - tiff:ColorSpace: "palette" - tiff:Compression: 1 - tiff:PhotometricInterpretation: 3 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 431 -Comparing "../oiio-images/libtiffpic/depth/flower-palette-02.tif" and "flower-palette-02.tif" -PASS -flower-palette-02.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: 52B3033465AA01129BAE149FF96CBB49877DAB7C -../oiio-images/libtiffpic/depth/flower-palette-02.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: 52B3033465AA01129BAE149FF96CBB49877DAB7C -Reading ../oiio-images/libtiffpic/depth/flower-palette-04.tif -../oiio-images/libtiffpic/depth/flower-palette-04.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: C6E40A3D134F1A29E153FE15459D8DE657CB7F9C - channel list: R, G, B - compression: "none" - DocumentName: "flower-palette-04.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 8 - tiff:BitsPerSample: 4 - tiff:ColorSpace: "palette" - tiff:Compression: 1 - tiff:PhotometricInterpretation: 3 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 221 -Comparing "../oiio-images/libtiffpic/depth/flower-palette-04.tif" and "flower-palette-04.tif" -PASS -flower-palette-04.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: C6E40A3D134F1A29E153FE15459D8DE657CB7F9C -../oiio-images/libtiffpic/depth/flower-palette-04.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: C6E40A3D134F1A29E153FE15459D8DE657CB7F9C -Reading ../oiio-images/libtiffpic/depth/flower-palette-08.tif -../oiio-images/libtiffpic/depth/flower-palette-08.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: 0ADA355BABFE9866F3D88AF7CA3AAC69D7DC036D - channel list: R, G, B - compression: "none" - DocumentName: "flower-palette-08.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 8 - tiff:ColorSpace: "palette" - tiff:Compression: 1 - tiff:PhotometricInterpretation: 3 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 112 -Comparing "../oiio-images/libtiffpic/depth/flower-palette-08.tif" and "flower-palette-08.tif" -PASS -flower-palette-08.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: 0ADA355BABFE9866F3D88AF7CA3AAC69D7DC036D -../oiio-images/libtiffpic/depth/flower-palette-08.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: 0ADA355BABFE9866F3D88AF7CA3AAC69D7DC036D -Reading ../oiio-images/libtiffpic/depth/flower-palette-16.tif -../oiio-images/libtiffpic/depth/flower-palette-16.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: 543285C6812105A1DA3B8ADA691D5DA3AE89B10D - channel list: R, G, B - compression: "none" - DocumentName: "flower-palette-16.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 16 - tiff:ColorSpace: "palette" - tiff:Compression: 1 - tiff:PhotometricInterpretation: 3 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 56 -Comparing "../oiio-images/libtiffpic/depth/flower-palette-16.tif" and "flower-palette-16.tif" -PASS -flower-palette-16.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: 543285C6812105A1DA3B8ADA691D5DA3AE89B10D -../oiio-images/libtiffpic/depth/flower-palette-16.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: 543285C6812105A1DA3B8ADA691D5DA3AE89B10D -Reading ../oiio-images/libtiffpic/depth/flower-rgb-contig-02.tif -../oiio-images/libtiffpic/depth/flower-rgb-contig-02.tif : 73 x 43, 3 channel, uint2 tiff - SHA-1: D68490C8E508DEBECEE4DF3A9E5DA0523CD5C302 - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-contig-02.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 2 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 148 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-contig-02.tif" and "flower-rgb-contig-02.tif" -PASS -flower-rgb-contig-02.tif : 73 x 43, 3 channel, uint2 tiff - SHA-1: D68490C8E508DEBECEE4DF3A9E5DA0523CD5C302 -../oiio-images/libtiffpic/depth/flower-rgb-contig-02.tif : 73 x 43, 3 channel, uint2 tiff - SHA-1: D68490C8E508DEBECEE4DF3A9E5DA0523CD5C302 -Reading ../oiio-images/libtiffpic/depth/flower-rgb-contig-04.tif -../oiio-images/libtiffpic/depth/flower-rgb-contig-04.tif : 73 x 43, 3 channel, uint4 tiff - SHA-1: A5920C9D08B9E25C96D55FDF72F46F589AE7643D - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-contig-04.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 4 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 74 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-contig-04.tif" and "flower-rgb-contig-04.tif" -PASS -flower-rgb-contig-04.tif : 73 x 43, 3 channel, uint4 tiff - SHA-1: A5920C9D08B9E25C96D55FDF72F46F589AE7643D -../oiio-images/libtiffpic/depth/flower-rgb-contig-04.tif : 73 x 43, 3 channel, uint4 tiff - SHA-1: A5920C9D08B9E25C96D55FDF72F46F589AE7643D -Reading ../oiio-images/libtiffpic/depth/flower-rgb-contig-08.tif -../oiio-images/libtiffpic/depth/flower-rgb-contig-08.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: 35D01526DE5F904B7978B8EA16192A28389E276F - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-contig-08.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 8 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 37 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-contig-08.tif" and "flower-rgb-contig-08.tif" -PASS -flower-rgb-contig-08.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: 35D01526DE5F904B7978B8EA16192A28389E276F -../oiio-images/libtiffpic/depth/flower-rgb-contig-08.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: 35D01526DE5F904B7978B8EA16192A28389E276F -Reading ../oiio-images/libtiffpic/depth/flower-rgb-contig-10.tif -../oiio-images/libtiffpic/depth/flower-rgb-contig-10.tif : 73 x 43, 3 channel, uint10 tiff - SHA-1: 0C41DF861699CF536C581721EF17B01D1EFB5D86 - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-contig-10.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 10 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 29 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-contig-10.tif" and "flower-rgb-contig-10.tif" -PASS -flower-rgb-contig-10.tif : 73 x 43, 3 channel, uint10 tiff - SHA-1: 0C41DF861699CF536C581721EF17B01D1EFB5D86 -../oiio-images/libtiffpic/depth/flower-rgb-contig-10.tif : 73 x 43, 3 channel, uint10 tiff - SHA-1: 0C41DF861699CF536C581721EF17B01D1EFB5D86 -Reading ../oiio-images/libtiffpic/depth/flower-rgb-contig-12.tif -../oiio-images/libtiffpic/depth/flower-rgb-contig-12.tif : 73 x 43, 3 channel, uint12 tiff - SHA-1: E61083B50548C7D304A45735452FD05C1814677B - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-contig-12.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 12 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 24 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-contig-12.tif" and "flower-rgb-contig-12.tif" -PASS -flower-rgb-contig-12.tif : 73 x 43, 3 channel, uint12 tiff - SHA-1: E61083B50548C7D304A45735452FD05C1814677B -../oiio-images/libtiffpic/depth/flower-rgb-contig-12.tif : 73 x 43, 3 channel, uint12 tiff - SHA-1: E61083B50548C7D304A45735452FD05C1814677B -Reading ../oiio-images/libtiffpic/depth/flower-rgb-contig-14.tif -../oiio-images/libtiffpic/depth/flower-rgb-contig-14.tif : 73 x 43, 3 channel, uint14 tiff - SHA-1: DD060DA62BB8F5903C5087796B2D05A682BE8ADA - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-contig-14.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 14 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 21 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-contig-14.tif" and "flower-rgb-contig-14.tif" -PASS -flower-rgb-contig-14.tif : 73 x 43, 3 channel, uint14 tiff - SHA-1: DD060DA62BB8F5903C5087796B2D05A682BE8ADA -../oiio-images/libtiffpic/depth/flower-rgb-contig-14.tif : 73 x 43, 3 channel, uint14 tiff - SHA-1: DD060DA62BB8F5903C5087796B2D05A682BE8ADA -Reading ../oiio-images/libtiffpic/depth/flower-rgb-contig-16.tif -../oiio-images/libtiffpic/depth/flower-rgb-contig-16.tif : 73 x 43, 3 channel, uint16 tiff - SHA-1: 19F69706D5C52FC9510A3C20F9A43361FEF2AC9D - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-contig-16.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 16 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 18 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-contig-16.tif" and "flower-rgb-contig-16.tif" -PASS -flower-rgb-contig-16.tif : 73 x 43, 3 channel, uint16 tiff - SHA-1: 19F69706D5C52FC9510A3C20F9A43361FEF2AC9D -../oiio-images/libtiffpic/depth/flower-rgb-contig-16.tif : 73 x 43, 3 channel, uint16 tiff - SHA-1: 19F69706D5C52FC9510A3C20F9A43361FEF2AC9D -Reading ../oiio-images/libtiffpic/depth/flower-rgb-contig-24.tif -../oiio-images/libtiffpic/depth/flower-rgb-contig-24.tif : 73 x 43, 3 channel, uint24 tiff - SHA-1: 6234B3CE28DFDF0FE6B1BCC29F62393696AF79A5 - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-contig-24.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q8 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 24 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 12 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-contig-24.tif" and "flower-rgb-contig-24.tif" -PASS -flower-rgb-contig-24.tif : 73 x 43, 3 channel, uint24 tiff - SHA-1: 6234B3CE28DFDF0FE6B1BCC29F62393696AF79A5 -../oiio-images/libtiffpic/depth/flower-rgb-contig-24.tif : 73 x 43, 3 channel, uint24 tiff - SHA-1: 6234B3CE28DFDF0FE6B1BCC29F62393696AF79A5 -Reading ../oiio-images/libtiffpic/depth/flower-rgb-contig-32.tif -../oiio-images/libtiffpic/depth/flower-rgb-contig-32.tif : 73 x 43, 3 channel, uint tiff - SHA-1: 04DAF56E34180687DB7FA12E7EE8EC3A3E40DAB8 - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-contig-32.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 32 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 9 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-contig-32.tif" and "flower-rgb-contig-32.tif" -PASS -flower-rgb-contig-32.tif : 73 x 43, 3 channel, uint tiff - SHA-1: 04DAF56E34180687DB7FA12E7EE8EC3A3E40DAB8 -../oiio-images/libtiffpic/depth/flower-rgb-contig-32.tif : 73 x 43, 3 channel, uint tiff - SHA-1: 04DAF56E34180687DB7FA12E7EE8EC3A3E40DAB8 -Reading ../oiio-images/libtiffpic/depth/flower-rgb-planar-02.tif -../oiio-images/libtiffpic/depth/flower-rgb-planar-02.tif : 73 x 43, 3 channel, uint2 tiff - SHA-1: D68490C8E508DEBECEE4DF3A9E5DA0523CD5C302 - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-planar-02.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "separate" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 2 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 2 - tiff:RowsPerStrip: 431 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-planar-02.tif" and "flower-rgb-planar-02.tif" -PASS -flower-rgb-planar-02.tif : 73 x 43, 3 channel, uint2 tiff - SHA-1: D68490C8E508DEBECEE4DF3A9E5DA0523CD5C302 -../oiio-images/libtiffpic/depth/flower-rgb-planar-02.tif : 73 x 43, 3 channel, uint2 tiff - SHA-1: D68490C8E508DEBECEE4DF3A9E5DA0523CD5C302 -Reading ../oiio-images/libtiffpic/depth/flower-rgb-planar-04.tif -../oiio-images/libtiffpic/depth/flower-rgb-planar-04.tif : 73 x 43, 3 channel, uint4 tiff - SHA-1: A5920C9D08B9E25C96D55FDF72F46F589AE7643D - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-planar-04.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "separate" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 4 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 2 - tiff:RowsPerStrip: 221 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-planar-04.tif" and "flower-rgb-planar-04.tif" -PASS -flower-rgb-planar-04.tif : 73 x 43, 3 channel, uint4 tiff - SHA-1: A5920C9D08B9E25C96D55FDF72F46F589AE7643D -../oiio-images/libtiffpic/depth/flower-rgb-planar-04.tif : 73 x 43, 3 channel, uint4 tiff - SHA-1: A5920C9D08B9E25C96D55FDF72F46F589AE7643D -Reading ../oiio-images/libtiffpic/depth/flower-rgb-planar-08.tif -../oiio-images/libtiffpic/depth/flower-rgb-planar-08.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: 35D01526DE5F904B7978B8EA16192A28389E276F - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-planar-08.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "separate" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 8 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 2 - tiff:RowsPerStrip: 112 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-planar-08.tif" and "flower-rgb-planar-08.tif" -PASS -flower-rgb-planar-08.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: 35D01526DE5F904B7978B8EA16192A28389E276F -../oiio-images/libtiffpic/depth/flower-rgb-planar-08.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: 35D01526DE5F904B7978B8EA16192A28389E276F -Reading ../oiio-images/libtiffpic/depth/flower-rgb-planar-10.tif -../oiio-images/libtiffpic/depth/flower-rgb-planar-10.tif : 73 x 43, 3 channel, uint10 tiff - SHA-1: 0C41DF861699CF536C581721EF17B01D1EFB5D86 - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-planar-10.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "separate" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 10 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 2 - tiff:RowsPerStrip: 89 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-planar-10.tif" and "flower-rgb-planar-10.tif" -PASS -flower-rgb-planar-10.tif : 73 x 43, 3 channel, uint10 tiff - SHA-1: 0C41DF861699CF536C581721EF17B01D1EFB5D86 -../oiio-images/libtiffpic/depth/flower-rgb-planar-10.tif : 73 x 43, 3 channel, uint10 tiff - SHA-1: 0C41DF861699CF536C581721EF17B01D1EFB5D86 -Reading ../oiio-images/libtiffpic/depth/flower-rgb-planar-12.tif -../oiio-images/libtiffpic/depth/flower-rgb-planar-12.tif : 73 x 43, 3 channel, uint12 tiff - SHA-1: E61083B50548C7D304A45735452FD05C1814677B - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-planar-12.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "separate" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 12 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 2 - tiff:RowsPerStrip: 74 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-planar-12.tif" and "flower-rgb-planar-12.tif" -PASS -flower-rgb-planar-12.tif : 73 x 43, 3 channel, uint12 tiff - SHA-1: E61083B50548C7D304A45735452FD05C1814677B -../oiio-images/libtiffpic/depth/flower-rgb-planar-12.tif : 73 x 43, 3 channel, uint12 tiff - SHA-1: E61083B50548C7D304A45735452FD05C1814677B -Reading ../oiio-images/libtiffpic/depth/flower-rgb-planar-14.tif -../oiio-images/libtiffpic/depth/flower-rgb-planar-14.tif : 73 x 43, 3 channel, uint14 tiff - SHA-1: DD060DA62BB8F5903C5087796B2D05A682BE8ADA - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-planar-14.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "separate" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 14 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 2 - tiff:RowsPerStrip: 64 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-planar-14.tif" and "flower-rgb-planar-14.tif" -PASS -flower-rgb-planar-14.tif : 73 x 43, 3 channel, uint14 tiff - SHA-1: DD060DA62BB8F5903C5087796B2D05A682BE8ADA -../oiio-images/libtiffpic/depth/flower-rgb-planar-14.tif : 73 x 43, 3 channel, uint14 tiff - SHA-1: DD060DA62BB8F5903C5087796B2D05A682BE8ADA -Reading ../oiio-images/libtiffpic/depth/flower-rgb-planar-16.tif -../oiio-images/libtiffpic/depth/flower-rgb-planar-16.tif : 73 x 43, 3 channel, uint16 tiff - SHA-1: 19F69706D5C52FC9510A3C20F9A43361FEF2AC9D - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-planar-16.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "separate" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 16 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 2 - tiff:RowsPerStrip: 56 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-planar-16.tif" and "flower-rgb-planar-16.tif" -PASS -flower-rgb-planar-16.tif : 73 x 43, 3 channel, uint16 tiff - SHA-1: 19F69706D5C52FC9510A3C20F9A43361FEF2AC9D -../oiio-images/libtiffpic/depth/flower-rgb-planar-16.tif : 73 x 43, 3 channel, uint16 tiff - SHA-1: 19F69706D5C52FC9510A3C20F9A43361FEF2AC9D -Reading ../oiio-images/libtiffpic/depth/flower-rgb-planar-24.tif -../oiio-images/libtiffpic/depth/flower-rgb-planar-24.tif : 73 x 43, 3 channel, uint24 tiff - SHA-1: 6234B3CE28DFDF0FE6B1BCC29F62393696AF79A5 - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-planar-24.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "separate" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q8 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 24 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 2 - tiff:RowsPerStrip: 37 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-planar-24.tif" and "flower-rgb-planar-24.tif" -PASS -flower-rgb-planar-24.tif : 73 x 43, 3 channel, uint24 tiff - SHA-1: 6234B3CE28DFDF0FE6B1BCC29F62393696AF79A5 -../oiio-images/libtiffpic/depth/flower-rgb-planar-24.tif : 73 x 43, 3 channel, uint24 tiff - SHA-1: 6234B3CE28DFDF0FE6B1BCC29F62393696AF79A5 -Reading ../oiio-images/libtiffpic/depth/flower-rgb-planar-32.tif -../oiio-images/libtiffpic/depth/flower-rgb-planar-32.tif : 73 x 43, 3 channel, uint tiff - SHA-1: 04DAF56E34180687DB7FA12E7EE8EC3A3E40DAB8 - channel list: R, G, B - compression: "none" - DocumentName: "flower-rgb-planar-32.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "separate" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 32 - tiff:Compression: 1 - tiff:PhotometricInterpretation: 2 - tiff:PlanarConfiguration: 2 - tiff:RowsPerStrip: 28 -Comparing "../oiio-images/libtiffpic/depth/flower-rgb-planar-32.tif" and "flower-rgb-planar-32.tif" -PASS -flower-rgb-planar-32.tif : 73 x 43, 3 channel, uint tiff - SHA-1: 04DAF56E34180687DB7FA12E7EE8EC3A3E40DAB8 -../oiio-images/libtiffpic/depth/flower-rgb-planar-32.tif : 73 x 43, 3 channel, uint tiff - SHA-1: 04DAF56E34180687DB7FA12E7EE8EC3A3E40DAB8 -Reading ../oiio-images/libtiffpic/depth/flower-separated-contig-08.tif -../oiio-images/libtiffpic/depth/flower-separated-contig-08.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: F739D368D37AB99D237FA1358A2EECE913245226 - channel list: R, G, B - compression: "none" - DocumentName: "flower-separated-contig-08.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 8 - tiff:ColorSpace: "CMYK" - tiff:Compression: 1 - tiff:PhotometricInterpretation: 5 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 28 -Comparing "../oiio-images/libtiffpic/depth/flower-separated-contig-08.tif" and "flower-separated-contig-08.tif" -PASS -flower-separated-contig-08.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: F739D368D37AB99D237FA1358A2EECE913245226 -../oiio-images/libtiffpic/depth/flower-separated-contig-08.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: F739D368D37AB99D237FA1358A2EECE913245226 -Reading ../oiio-images/libtiffpic/depth/flower-separated-contig-16.tif -../oiio-images/libtiffpic/depth/flower-separated-contig-16.tif : 73 x 43, 3 channel, uint16 tiff - SHA-1: BBAA06ABCADF65F9323FDA979421A54F5B2E53D0 - channel list: R, G, B - compression: "none" - DocumentName: "flower-separated-contig-16.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "contig" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 16 - tiff:ColorSpace: "CMYK" - tiff:Compression: 1 - tiff:PhotometricInterpretation: 5 - tiff:PlanarConfiguration: 1 - tiff:RowsPerStrip: 14 -Comparing "../oiio-images/libtiffpic/depth/flower-separated-contig-16.tif" and "flower-separated-contig-16.tif" -PASS -flower-separated-contig-16.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: E55335D12E9A20EFB0A5EAE80F1801DF5A9BEE12 -../oiio-images/libtiffpic/depth/flower-separated-contig-16.tif : 73 x 43, 3 channel, uint16 tiff - SHA-1: BBAA06ABCADF65F9323FDA979421A54F5B2E53D0 -Reading ../oiio-images/libtiffpic/depth/flower-separated-planar-08.tif -../oiio-images/libtiffpic/depth/flower-separated-planar-08.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: F739D368D37AB99D237FA1358A2EECE913245226 - channel list: R, G, B - compression: "none" - DocumentName: "flower-separated-planar-08.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "separate" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 8 - tiff:ColorSpace: "CMYK" - tiff:Compression: 1 - tiff:PhotometricInterpretation: 5 - tiff:PlanarConfiguration: 2 - tiff:RowsPerStrip: 112 -Comparing "../oiio-images/libtiffpic/depth/flower-separated-planar-08.tif" and "flower-separated-planar-08.tif" -PASS -flower-separated-planar-08.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: F739D368D37AB99D237FA1358A2EECE913245226 -../oiio-images/libtiffpic/depth/flower-separated-planar-08.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: F739D368D37AB99D237FA1358A2EECE913245226 -Reading ../oiio-images/libtiffpic/depth/flower-separated-planar-16.tif -../oiio-images/libtiffpic/depth/flower-separated-planar-16.tif : 73 x 43, 3 channel, uint16 tiff - SHA-1: BBAA06ABCADF65F9323FDA979421A54F5B2E53D0 - channel list: R, G, B - compression: "none" - DocumentName: "flower-separated-planar-16.tif" - Orientation: 1 (normal) - PixelAspectRatio: 1 - planarconfig: "separate" - ResolutionUnit: "in" - Software: "GraphicsMagick 1.2 unreleased Q32 http://www.GraphicsMagick.org/" - XResolution: 72 - YResolution: 72 - oiio:BitsPerSample: 16 - tiff:ColorSpace: "CMYK" - tiff:Compression: 1 - tiff:PhotometricInterpretation: 5 - tiff:PlanarConfiguration: 2 - tiff:RowsPerStrip: 56 -Comparing "../oiio-images/libtiffpic/depth/flower-separated-planar-16.tif" and "flower-separated-planar-16.tif" -PASS -flower-separated-planar-16.tif : 73 x 43, 3 channel, uint8 tiff - SHA-1: E55335D12E9A20EFB0A5EAE80F1801DF5A9BEE12 -../oiio-images/libtiffpic/depth/flower-separated-planar-16.tif : 73 x 43, 3 channel, uint16 tiff - SHA-1: BBAA06ABCADF65F9323FDA979421A54F5B2E53D0 -Comparing "cmyk_as_cmyk.tif" and "ref/cmyk_as_cmyk.tif" -PASS diff --git a/testsuite/tiff-suite/ref/out-alt.txt b/testsuite/tiff-suite/ref/out-alt.txt index 20a7ce2efb..5a65b8c5b9 100644 --- a/testsuite/tiff-suite/ref/out-alt.txt +++ b/testsuite/tiff-suite/ref/out-alt.txt @@ -68,9 +68,11 @@ Reading ../oiio-images/libtiffpic/dscf0013.tif Exif:ColorSpace: 1 Exif:DateTimeDigitized: "2004:11:10 00:00:31" Exif:DateTimeOriginal: "2004:11:10 00:00:31" + Exif:ExifVersion: "0210" Exif:ExposureBiasValue: 0 Exif:ExposureProgram: 2 (normal program) Exif:Flash: 1 (flash fired) + Exif:FlashPixVersion: "0100" Exif:FocalLength: 7.4 (7.4 mm) Exif:FocalPlaneResolutionUnit: 3 (cm) Exif:FocalPlaneXResolution: 847 @@ -238,10 +240,12 @@ Reading ../oiio-images/libtiffpic/pc260001.tif Exif:DateTimeDigitized: "2005:12:26 17:09:35" Exif:DateTimeOriginal: "2005:12:26 17:09:35" Exif:DigitalZoomRatio: 0 + Exif:ExifVersion: "0221" Exif:ExposureBiasValue: 0 Exif:ExposureMode: 0 (auto) Exif:ExposureProgram: 2 (normal program) Exif:Flash: 89 (flash fired, auto flash, red-eye reduction) + Exif:FlashPixVersion: "0100" Exif:FocalLength: 17.8 (17.8 mm) Exif:LightSource: 0 (unknown) Exif:MaxApertureValue: 3 (f/2.8) diff --git a/testsuite/tiff-suite/ref/out-alt2.txt b/testsuite/tiff-suite/ref/out-alt2.txt index 3a14b41bc5..ad3878cc23 100644 --- a/testsuite/tiff-suite/ref/out-alt2.txt +++ b/testsuite/tiff-suite/ref/out-alt2.txt @@ -68,9 +68,11 @@ Reading ../oiio-images/libtiffpic/dscf0013.tif Exif:ColorSpace: 1 Exif:DateTimeDigitized: "2004:11:10 00:00:31" Exif:DateTimeOriginal: "2004:11:10 00:00:31" + Exif:ExifVersion: "0210" Exif:ExposureBiasValue: 0 Exif:ExposureProgram: 2 (normal program) Exif:Flash: 1 (flash fired) + Exif:FlashPixVersion: "0100" Exif:FocalLength: 7.4 (7.4 mm) Exif:FocalPlaneResolutionUnit: 3 (cm) Exif:FocalPlaneXResolution: 847 @@ -238,10 +240,12 @@ Reading ../oiio-images/libtiffpic/pc260001.tif Exif:DateTimeDigitized: "2005:12:26 17:09:35" Exif:DateTimeOriginal: "2005:12:26 17:09:35" Exif:DigitalZoomRatio: 0 + Exif:ExifVersion: "0221" Exif:ExposureBiasValue: 0 Exif:ExposureMode: 0 (auto) Exif:ExposureProgram: 2 (normal program) Exif:Flash: 89 (flash fired, auto flash, red-eye reduction) + Exif:FlashPixVersion: "0100" Exif:FocalLength: 17.8 (17.8 mm) Exif:LightSource: 0 (unknown) Exif:MaxApertureValue: 3 (f/2.8) diff --git a/testsuite/tiff-suite/ref/out-jpeg9b.txt b/testsuite/tiff-suite/ref/out-jpeg9b.txt index 354a70c52e..494a935621 100644 --- a/testsuite/tiff-suite/ref/out-jpeg9b.txt +++ b/testsuite/tiff-suite/ref/out-jpeg9b.txt @@ -68,9 +68,11 @@ Reading ../oiio-images/libtiffpic/dscf0013.tif Exif:ColorSpace: 1 Exif:DateTimeDigitized: "2004:11:10 00:00:31" Exif:DateTimeOriginal: "2004:11:10 00:00:31" + Exif:ExifVersion: "0210" Exif:ExposureBiasValue: 0 Exif:ExposureProgram: 2 (normal program) Exif:Flash: 1 (flash fired) + Exif:FlashPixVersion: "0100" Exif:FocalLength: 7.4 (7.4 mm) Exif:FocalPlaneResolutionUnit: 3 (cm) Exif:FocalPlaneXResolution: 847 @@ -238,10 +240,12 @@ Reading ../oiio-images/libtiffpic/pc260001.tif Exif:DateTimeDigitized: "2005:12:26 17:09:35" Exif:DateTimeOriginal: "2005:12:26 17:09:35" Exif:DigitalZoomRatio: 0 + Exif:ExifVersion: "0221" Exif:ExposureBiasValue: 0 Exif:ExposureMode: 0 (auto) Exif:ExposureProgram: 2 (normal program) Exif:Flash: 89 (flash fired, auto flash, red-eye reduction) + Exif:FlashPixVersion: "0100" Exif:FocalLength: 17.8 (17.8 mm) Exif:LightSource: 0 (unknown) Exif:MaxApertureValue: 3 (f/2.8) diff --git a/testsuite/tiff-suite/ref/out-jpeg9d-alt.txt b/testsuite/tiff-suite/ref/out-jpeg9d-alt.txt index fb5da67d19..423c4a2218 100644 --- a/testsuite/tiff-suite/ref/out-jpeg9d-alt.txt +++ b/testsuite/tiff-suite/ref/out-jpeg9d-alt.txt @@ -68,9 +68,11 @@ Reading ../oiio-images/libtiffpic/dscf0013.tif Exif:ColorSpace: 1 Exif:DateTimeDigitized: "2004:11:10 00:00:31" Exif:DateTimeOriginal: "2004:11:10 00:00:31" + Exif:ExifVersion: "0210" Exif:ExposureBiasValue: 0 Exif:ExposureProgram: 2 (normal program) Exif:Flash: 1 (flash fired) + Exif:FlashPixVersion: "0100" Exif:FocalLength: 7.4 (7.4 mm) Exif:FocalPlaneResolutionUnit: 3 (cm) Exif:FocalPlaneXResolution: 847 @@ -238,10 +240,12 @@ Reading ../oiio-images/libtiffpic/pc260001.tif Exif:DateTimeDigitized: "2005:12:26 17:09:35" Exif:DateTimeOriginal: "2005:12:26 17:09:35" Exif:DigitalZoomRatio: 0 + Exif:ExifVersion: "0221" Exif:ExposureBiasValue: 0 Exif:ExposureMode: 0 (auto) Exif:ExposureProgram: 2 (normal program) Exif:Flash: 89 (flash fired, auto flash, red-eye reduction) + Exif:FlashPixVersion: "0100" Exif:FocalLength: 17.8 (17.8 mm) Exif:LightSource: 0 (unknown) Exif:MaxApertureValue: 3 (f/2.8) diff --git a/testsuite/tiff-suite/ref/out.txt b/testsuite/tiff-suite/ref/out.txt index 30a318664a..a28ac71677 100644 --- a/testsuite/tiff-suite/ref/out.txt +++ b/testsuite/tiff-suite/ref/out.txt @@ -68,9 +68,11 @@ Reading ../oiio-images/libtiffpic/dscf0013.tif Exif:ColorSpace: 1 Exif:DateTimeDigitized: "2004:11:10 00:00:31" Exif:DateTimeOriginal: "2004:11:10 00:00:31" + Exif:ExifVersion: "0210" Exif:ExposureBiasValue: 0 Exif:ExposureProgram: 2 (normal program) Exif:Flash: 1 (flash fired) + Exif:FlashPixVersion: "0100" Exif:FocalLength: 7.4 (7.4 mm) Exif:FocalPlaneResolutionUnit: 3 (cm) Exif:FocalPlaneXResolution: 847 @@ -238,10 +240,12 @@ Reading ../oiio-images/libtiffpic/pc260001.tif Exif:DateTimeDigitized: "2005:12:26 17:09:35" Exif:DateTimeOriginal: "2005:12:26 17:09:35" Exif:DigitalZoomRatio: 0 + Exif:ExifVersion: "0221" Exif:ExposureBiasValue: 0 Exif:ExposureMode: 0 (auto) Exif:ExposureProgram: 2 (normal program) Exif:Flash: 89 (flash fired, auto flash, red-eye reduction) + Exif:FlashPixVersion: "0100" Exif:FocalLength: 17.8 (17.8 mm) Exif:LightSource: 0 (unknown) Exif:MaxApertureValue: 3 (f/2.8) diff --git a/testsuite/webp/ref/out-webp1.1.txt b/testsuite/webp/ref/out-webp1.1.txt index f7e0d095de..92ec6d9f0f 100644 --- a/testsuite/webp/ref/out-webp1.1.txt +++ b/testsuite/webp/ref/out-webp1.1.txt @@ -18,3 +18,9 @@ Reading ../oiio-images/webp/4.webp SHA-1: 8F42E3DCCE6FE15146BA06C440C15B7831F60572 channel list: R, G, B oiio:ColorSpace: "srgb_rec709_scene" +Reading rgba.webp +rgba.webp : 64 x 64, 4 channel, uint8 webp + SHA-1: 897256B6709E1A4DA9DABA92B6BDE39CCFCCD8C1 + channel list: R, G, B, A + oiio:ColorSpace: "srgb_rec709_scene" + oiio:UnassociatedAlpha: 1 diff --git a/testsuite/webp/run.py b/testsuite/webp/run.py index a0a5c53510..3e61dd5d81 100755 --- a/testsuite/webp/run.py +++ b/testsuite/webp/run.py @@ -11,3 +11,6 @@ # a lossy format and is not stable under the round trip # command += rw_command (OIIO_TESTSUITE_IMAGEDIR, f, # extraargs='-attrib compression lossless') + +command += oiiotool ("--create 64x64 4 -o rgba.webp") +command += info_command ("rgba.webp", "--iconfig oiio:UnassociatedAlpha 1", safematch=True)