diff --git a/.bazelversion b/.bazelversion index 91e4a9f262..a8907c025d 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -6.3.2 +7.0.2 diff --git a/.github/workflows/analysis_workflow.yml b/.github/workflows/analysis_workflow.yml index 17712af634..c42bbdf11d 100644 --- a/.github/workflows/analysis_workflow.yml +++ b/.github/workflows/analysis_workflow.yml @@ -86,7 +86,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - run: sonar-scanner -X -Dsonar.login=$SONAR_TOKEN + run: sonar-scanner -X # ------------------------------------------------------------------------------ # Valgrind memcheck test diff --git a/.github/workflows/bazel_build.yml b/.github/workflows/bazel_build.yml index 6ed03b4c95..023dcfece6 100644 --- a/.github/workflows/bazel_build.yml +++ b/.github/workflows/bazel_build.yml @@ -45,10 +45,10 @@ jobs: runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4.1.1 - name: Mount Bazel cache - uses: actions/cache@v3 + uses: actions/cache@v4.0.0 with: path: "/home/runner/.cache/bazel" key: bazel-ubuntu-22 @@ -57,19 +57,16 @@ jobs: run: | bazelisk build //... bazelisk test //... - # Test bzlmod - bazelisk build --enable_bzlmod -- //... - bazelisk test --enable_bzlmod -- //... build_and_test_windows: name: Windows Server 2022 build runs-on: windows-2022 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4.1.1 - name: Mount Bazel cache - uses: actions/cache@v3 + uses: actions/cache@v4.0.0 with: path: "/home/runner/.cache/bazel" key: bazel-windows-2022 @@ -78,19 +75,16 @@ jobs: run: | bazelisk build //... bazelisk test //... - # Test bzlmod - bazelisk build --enable_bzlmod -- //... - bazelisk test --enable_bzlmod -- //... build_and_test_macos: name: macOS 13 Bazel build runs-on: macos-13 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4.1.1 - name: Mount Bazel cache - uses: actions/cache@v3 + uses: actions/cache@v4.0.0 with: path: "/home/runner/.cache/bazel" key: bazel-macos-13 @@ -99,6 +93,21 @@ jobs: run: | bazelisk build //... bazelisk test //... - # Test bzlmod - bazelisk build --enable_bzlmod -- //... - bazelisk test --enable_bzlmod -- //... + + build_and_test_macos_M1: + name: macOS 14 Bazel build + runs-on: macos-14 + + steps: + - uses: actions/checkout@v4.1.1 + + - name: Mount Bazel cache + uses: actions/cache@v4.0.0 + with: + path: "/home/runner/.cache/bazel" + key: bazel-macos-14 + + - name: Build + run: | + bazelisk build //... + bazelisk test //... diff --git a/.github/workflows/ci_workflow.yml b/.github/workflows/ci_workflow.yml index 3aac7583e8..33670f472c 100644 --- a/.github/workflows/ci_workflow.yml +++ b/.github/workflows/ci_workflow.yml @@ -25,6 +25,7 @@ on: - 'website/src/**' - '!bazel/**' - '!src/wrappers/**' + - '!.github/workflows/python-**.yml' pull_request: branches-ignore: - RB-2.* @@ -38,6 +39,7 @@ on: - 'website/src/**' - '!bazel/**' - '!src/wrappers/**' + - '!.github/workflows/python-**.yml' permissions: contents: read @@ -238,8 +240,8 @@ jobs: run: yum install -y help2man - name: Configure run: | - cmake .. \ - -DCMAKE_INSTALL_PREFIX=../_install \ + cmake -B _build -S . \ + -DCMAKE_INSTALL_PREFIX=_install \ -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} \ -DCMAKE_CXX_STANDARD=${{ matrix.cxx-standard }} \ -DCMAKE_CXX_FLAGS=${{ matrix.cxx-flags }} \ @@ -249,13 +251,11 @@ jobs: -DOPENEXR_INSTALL_DOCS='ON' \ -DOPENEXR_RUN_FUZZ_TESTS='OFF' \ -DOPENEXR_ENABLE_THREADING=${{ matrix.threads-enabled }} - working-directory: _build - name: Build run: | - cmake --build . \ + cmake --build _build \ --target install \ --config ${{ matrix.build-type }} - working-directory: _build - name: Validate run: | share/ci/scripts/linux/validate_openexr_libs.sh _install diff --git a/.github/workflows/python-wheels-publish-test.yml b/.github/workflows/python-wheels-publish-test.yml new file mode 100644 index 0000000000..cef33e9cc6 --- /dev/null +++ b/.github/workflows/python-wheels-publish-test.yml @@ -0,0 +1,104 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) Contributors to the OpenEXR Project. + +name: Publish python distribution 📦 to TestPyPI + +on: + + # Publish python wheels to test.pypi when a release candidate is tagged, + # e.g. v3.4.5-rc, v3.4.5-rc6, etc. + + push: + tags: + - v3.[0-9]+.[0-9]+-rc* + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + name: Python Wheels - ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + + environment: + name: testpypi + url: https://test.pypi.org/p/openexr + + permissions: + id-token: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Create sdist + # Only create it once. + if: ${{ matrix.os == 'ubuntu-latest' }} + env: + OPENEXR_RELEASE_CANDIDATE_TAG: ${{ github.ref_name }} + run: pipx run build --sdist . --outdir wheelhouse + + - name: Build wheel + uses: pypa/cibuildwheel@v2.16 + with: + output-dir: wheelhouse + env: + CIBW_ARCHS_LINUX: x86_64 + CIBW_ARCHS_MACOS: x86_64 arm64 universal2 + # Skip python 3.6 since scikit-build-core requires 3.7+ + # Skip 32-bit wheels builds on Windows + # Also skip the PyPy builds, since they fail the unit tests + CIBW_SKIP: cp36-* *-win32 *_i686 pp* + CIBW_TEST_SKIP: "*-macosx_universal2:arm64" + CIBW_ENVIRONMENT: OPENEXR_RELEASE_CANDIDATE_TAG="${{ github.ref_name }}" + + - name: Upload artifact + uses: actions/upload-artifact@v4.0.0 + with: + name: wheels-${{ matrix.os }} + path: | + ./wheelhouse/*.whl + ./wheelhouse/*.tar.gz + + publish-to-testpypi: + name: Publish Python 🐍 distribution 📦 to TestPyPI + needs: + - build + runs-on: ubuntu-latest + + environment: + name: testpypi + url: https://test.pypi.org/p/openexr + + permissions: + id-token: write + + steps: + - name: Download Linux artifacts + uses: actions/download-artifact@v4.0.0 + with: + name: wheels-ubuntu-latest + path: dist + - name: Download macOS artifacts + uses: actions/download-artifact@v4.0.0 + with: + name: wheels-macos-latest + path: dist + - name: Download Windows artifacts + uses: actions/download-artifact@v4.0.0 + with: + name: wheels-windows-latest + path: dist + - name: Publish distribution 📦 to TestPyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + repository-url: https://test.pypi.org/legacy/ \ No newline at end of file diff --git a/.github/workflows/python-wheels-publish.yml b/.github/workflows/python-wheels-publish.yml new file mode 100644 index 0000000000..2330847df3 --- /dev/null +++ b/.github/workflows/python-wheels-publish.yml @@ -0,0 +1,96 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) Contributors to the OpenEXR Project. + +name: Publish python distribution 📦 to PyPI + +on: + # Publish wheels to pypi on release + release: + types: [published] + workflow_dispatch: + +permissions: + contents: read + +jobs: + build: + name: Python Wheels - ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + + environment: + name: pypi + url: https://pypi.org/p/openexr + + permissions: + id-token: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' + + - name: Create sdist + # Only create it once. + if: ${{ matrix.os == 'ubuntu-latest' }} + run: pipx run build --sdist . --outdir wheelhouse + + - name: Build wheel + uses: pypa/cibuildwheel@v2.16 + with: + output-dir: wheelhouse + env: + CIBW_BUILD: cp312-* + CIBW_ARCHS_LINUX: x86_64 + CIBW_ARCHS_MACOS: x86_64 arm64 universal2 + # Skip python 3.6 since scikit-build-core requires 3.7+ + # Skip 32-bit wheels builds on Windows + # Also skip the PyPy builds, since they fail the unit tests + CIBW_SKIP: cp36-* *-win32 *_i686 pp* + CIBW_TEST_SKIP: "*arm64" + + - name: Upload artifact + uses: actions/upload-artifact@v4.0.0 + with: + name: wheels-${{ matrix.os }} + path: | + ./wheelhouse/*.whl + ./wheelhouse/*.tar.gz + + publish-to-pypi: + name: Publish Python 🐍 distribution 📦 to PyPI + needs: + - build + runs-on: ubuntu-latest + + environment: + name: pypi + url: https://pypi.org/p/openexr + + permissions: + id-token: write + + steps: + - name: Download Linux artifacts + uses: actions/download-artifact@v4.0.0 + with: + name: wheels-ubuntu-latest + path: dist + - name: Download macOS artifacts + uses: actions/download-artifact@v4.0.0 + with: + name: wheels-macos-latest + path: dist + - name: Download Windows artifacts + uses: actions/download-artifact@v4.0.0 + with: + name: wheels-windows-latest + path: dist + - name: Publish distribution 📦 to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 diff --git a/.github/workflows/python-wheels.yml b/.github/workflows/python-wheels.yml index cc926ef5c7..516af01390 100644 --- a/.github/workflows/python-wheels.yml +++ b/.github/workflows/python-wheels.yml @@ -1,91 +1,74 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) Contributors to the OpenEXR Project. -# -name: Python wheels +name: Python Wheels on: + + # Run on all changes (PR and push) to the python binding + # source/configuration files, except on the release branches, which + # have their own workflow, which also publish to pypi/test.pypi. + # Note that changes to the core libraries will *not* + # trigger building the wheels. However, the main ci workflow does + # build and test the bindings (for a single python version on a + # single arch) + push: branches-ignore: - - RB-2.* - tags-ignore: - - v1.* - - v2.* + - RB-* paths: - - '**' - - '!**.md' - - '!website/**' - - 'website/src/**' - - '!bazel/**' + - 'src/wrappers/python/**' + - 'pyproject.toml' + - '.github/workflows/python-wheels.yml' pull_request: branches-ignore: - - RB-2.* - tags-ignore: - - v1.* - - v2.* + - RB-* paths: - - '**' - - '!**.md' - - '!website/**' - - 'website/src/**' - - '!bazel/**' + - 'src/wrappers/python/**' + - 'pyproject.toml' + - '.github/workflows/python-wheels.yml' permissions: contents: read jobs: build_wheels: - name: Build Python wheels + name: Python Wheels - ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: - os: [ubuntu-22.04, windows-latest, macOS-latest] - env: - # On macOS we build both x86 and arm to support Intel and Apple Silicon. - CIBW_ARCHS_MACOS: x86_64 arm64 - # Skip 32-bit wheels builds on Windows. - # Also skip the PyPy builds, since they fail the unittests - CIBW_SKIP: "*-win32 *_i686 pp*" - # The CI platform is Intel based so we are doing cross compilation - # for arm64. It is not currently possible to test arm64 when cross - # compiling. - CIBW_TEST_SKIP: "*_arm64" - CIBW_BEFORE_BUILD: > - echo "Installing OpenEXR..." && - cd openexr.build && - cmake -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=../openexr.install -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON -DCMAKE_PREFIX_PATH=../openexr.install -DCMAKE_INSTALL_LIBDIR=lib -DBUILD_TESTING=OFF -DOPENEXR_INSTALL_EXAMPLES=OFF -DOPENEXR_BUILD_TOOLS=OFF -DBUILD_SHARED_LIBS=OFF -DOPENEXR_FORCE_INTERNAL_DEFLATE=ON -DOPENEXR_FORCE_INTERNAL_IMATH=ON -DCMAKE_POSITION_INDEPENDENT_CODE=ON ../ && - cmake --build ./ --config Release --clean-first && - cmake --install ./ --config Release && - cd .. - CIBW_TEST_REQUIRES: pytest - CIBW_TEST_COMMAND: pytest {project}/src/wrappers/python/tests/ + os: [ubuntu-latest, macos-latest, windows-latest] steps: - - uses: actions/checkout@v3 - - # Used to host cibuildwheel - - uses: actions/setup-python@v4 - with: - python-version: '3.x' - - name: Install cibuildwheel - run: python -m pip install cibuildwheel==2.16.2 + - name: Checkout + uses: actions/checkout@v4 - - name: Create setup.py - run: | - mv ${{github.workspace}}/src/wrappers/python/setup.py ${{github.workspace}}/setup.py - mv ${{github.workspace}}/src/wrappers/python/Imath.py ${{github.workspace}}/Imath.py - mv ${{github.workspace}}/src/wrappers/python/OpenEXR.cpp ${{github.workspace}}/OpenEXR.cpp + - name: Install Python + uses: actions/setup-python@v5 + with: + python-version: '3.x' - - name: Create folders - run: | - mkdir -p ${{github.workspace}}/openexr.build - mkdir -p ${{github.workspace}}/openexr.install + - name: Create sdist + # Only create it once. + if: ${{ matrix.os == 'ubuntu-latest' }} + run: pipx run build --sdist . --outdir wheelhouse - - name: Build wheels - run: python -m cibuildwheel --output-dir wheelhouse + - name: Build wheel + uses: pypa/cibuildwheel@v2.16 + env: + CIBW_ARCHS_MACOS: x86_64 arm64 universal2 + # Skip python 3.6 since scikit-build-core requires 3.7+ + # Skip 32-bit wheels builds on Windows + # Also skip the PyPy builds, since they fail the unit tests + CIBW_SKIP: cp36-* *-win32 *_i686 pp* + CIBW_TEST_SKIP: "*-macosx*arm64" - - uses: actions/upload-artifact@v3 + - name: Upload artifact + uses: actions/upload-artifact@v4 with: - name: "Python wheels" - path: ./wheelhouse/*.whl + name: wheels-${{ matrix.os }} + path: | + ./wheelhouse/*.whl + ./wheelhouse/*.tar.gz + diff --git a/.github/workflows/release-notice.yml b/.github/workflows/release-notice.yml new file mode 100644 index 0000000000..f7e32a9499 --- /dev/null +++ b/.github/workflows/release-notice.yml @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) Contributors to the OpenEXR Project. + +name: Publish Release Notice to ASWF Slack + +on: + release: + types: + - published + # published should cover both 'released' and 'prereleased' + +jobs: + publish: + runs-on: ubuntu-latest + timeout-minutes: 5 + + steps: + - name: 'Notify Slack #release-announcements' + id: slack1 + with: + project_name: "OpenEXR" + slack_bot_token: ${{ secrets.SLACK_BOT_TOKEN }} + slack_channel: "#release-announcements" + project_logo: "https://artwork.aswf.io/projects/openexr/icon/color/openexr-icon-color.png" + uses: jmertic/slack-release-notifier@main + + - name: 'Notify Slack #openexr' + id: slack2 + with: + project_name: "OpenEXR" + slack_bot_token: ${{ secrets.SLACK_BOT_TOKEN }} + slack_channel: "#openexr" + project_logo: "https://artwork.aswf.io/projects/openexr/icon/color/openexr-icon-color.png" + uses: jmertic/slack-release-notifier@main + \ No newline at end of file diff --git a/.github/workflows/release-sign.yml b/.github/workflows/release-sign.yml new file mode 100644 index 0000000000..49fc8b9112 --- /dev/null +++ b/.github/workflows/release-sign.yml @@ -0,0 +1,54 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) Contributors to the OpenEXR Project. + +# +# Releases are signed via https://github.com/sigstore/sigstore-python. +# See https://docs.sigstore.dev for information about sigstore. +# +# This action creates a .tar.gz of the complete OpenEXR source tree at +# the given release tag, signs it via sigstore, and uploads the +# .tar.gz and the associated .tar.gz.sigstore credential bundle. +# +# To verify a downloaded release at a given tag: +# +# % pip install sigstore +# % sigstore verify github --cert-identity https://github.com/AcademySoftwareFoundation/openexr/.github/workflows/release-sign.yml@refs/tags/ openexr-.tar.gz +# + +name: Sign Release + +on: + release: + types: [published] + +permissions: + contents: write + id-token: write + repository-projects: write + +jobs: + release: + name: Sign & upload release artifacts + runs-on: ubuntu-latest + + env: + tarball: openexr-${{ github.ref_name }}.tar.gz + + steps: + + - name: Checkout + uses: actions/checkout@v2 + + - name: Create archive + run: git archive --format=tar.gz -o ${{ env.tarball }} ${{ github.ref_name }} + + - name: Sign archive with Sigstore + uses: sigstore/gh-action-sigstore-python@v2.1.1 + with: + inputs: ${{ env.tarball }} + + - name: Upload release archive + env: + GH_TOKEN: ${{ github.token }} + run: gh release upload ${{ github.ref_name }} ${{ env.tarball }} ${{ env.tarball }}.sigstore + diff --git a/.github/workflows/snyk-scan-cron.yml b/.github/workflows/snyk-scan-cron.yml new file mode 100644 index 0000000000..52671c89b5 --- /dev/null +++ b/.github/workflows/snyk-scan-cron.yml @@ -0,0 +1,40 @@ +--- +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) Contributors to the OpenEXR Project. + +name: Snyk Scan Code + +on: + # https://docs.github.com/en/actions/learn-github-actions/workflow-syntax-for-github-actions + schedule: + - cron: "0 4 * * 0" + +jobs: + snyk-scan-pr: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: snyk/actions/setup@master + id: snyk + + - name: Snyk version + run: echo "${{ steps.snyk.outputs.version }}" + + - name: Snyk Auth + run: snyk auth ${{ secrets.SNYK_TOKEN }} + + - name: Snyk Scan Code + # Scan the C/C++ code for vulnerabilities using the Snyk CLI with the unmanaged flag + # https://docs.snyk.io/scan-using-snyk/supported-languages-and-frameworks/c-c++ for options + run: snyk test --unmanaged --print-dep-paths --org=${{ secrets.SNYK_ORG }} + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + continue-on-error: true # optional + + - name: Monitor for Vulnerabilities + # To import the test results (issues and dependencies) in the Snyk CLI, run the snyk monitor --unmanaged command: + run: snyk monitor --unmanaged --org=${{ secrets.SNYK_ORG }} + env: + SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} + continue-on-error: true # optional diff --git a/.github/workflows/website_preview_link.yml b/.github/workflows/website_preview_link.yml new file mode 100644 index 0000000000..84600ceee2 --- /dev/null +++ b/.github/workflows/website_preview_link.yml @@ -0,0 +1,35 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) Contributors to the OpenEXR Project. +# +# GitHub Actions workflow file +# https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions + +# +# This action adds a link to the PR description pointing to the +# readthedocs build of the website, for PRs that modify the website. +# +# Note that the link is also available in the PR checks, but it gets +# buried among the output of the checks and isn't obvious. +# + +name: Website preview link +on: + pull_request_target: + types: + - opened + paths: + - 'website/**' + +permissions: + pull-requests: write + +jobs: + pull-request-links: + runs-on: ubuntu-latest + steps: + - uses: readthedocs/actions/preview@v1 + with: + project-slug: "openexr" + message-template: "Website preview: {docs-pr-index-url}" + + diff --git a/.github/workflows/website_workflow.yml b/.github/workflows/website_workflow.yml index afd69e38d3..f960d97c44 100644 --- a/.github/workflows/website_workflow.yml +++ b/.github/workflows/website_workflow.yml @@ -10,10 +10,14 @@ name: Website # Skip the release branches, since the website is built from main. on: + workflow_dispatch: + push: branches:-ignore: - RB-2.* - RB-3.* + tags-ignore: + - v3.[0-9]+.[0-9]+-rc* paths: - 'website/**' @@ -21,6 +25,8 @@ on: branches:-ignore: - RB-2.* - RB-3.* + tags-ignore: + - v3.[0-9]+.[0-9]+-rc* paths: - 'website/**' diff --git a/.gitignore b/.gitignore index 3669dd533e..15be45fcfe 100644 --- a/.gitignore +++ b/.gitignore @@ -57,7 +57,4 @@ docs/_test_images/ # Ignore Bazel generated files bazel-* - -cmake-build-debug/ - -.idea/ +MODULE.bazel.lock diff --git a/.mailmap b/.mailmap new file mode 100644 index 0000000000..0a4af87add --- /dev/null +++ b/.mailmap @@ -0,0 +1,163 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) Contributors to the OpenEXR Project. +# +# This file establishes email equivalences so we can resolve what look like +# multiple authors, but actually are the same author who has used multiple +# emails over the course of their involvement with the project. +# +# The format is any of the following: +# +# CANONICAL-NAME +# CANONICAL-NAME alternate-name +# +# You can check for duplicates with this command: +# git shortlog -sne --all +# That command (and others) will use this file to collapse the duplicates. +# +# If you see any duplicates we don't account for here, or if you look at your +# own entry here and want a different name or email to be your canonical one +# (we may not have guessed correctly and matched your preferences), please +# file a PR with the edits to this file. + +Aaron Demolder +Abe Fettig +Aloys Baillet aloysb +Andrew Kunz +Anton Dukhovnikov Anton Dukhovnikov <131838425+antond-weta@users.noreply.github.com> +Antonio Rojas +Aras Pranckevičius Aras Pranckevičius +Aras Pranckevičius Aras Pranckevičius +Arkady Shapkin +Arkell Rasiah Arkell Rasiah +Arkell Rasiah Arkell Rasiah +Axel Waggershauser +axxel +Balázs Oroszi +Barnaby Robson Barnaby Robson +Ben Grimes <72311676+MrGlobby@users.noreply.github.com> +Bernd +Brendan Bolles +CAHEK7 +Cary Phillips cary-ilm +Cary Phillips Cary Phillips +Cary Phillips seabeepea +Catherine +Chris Leu cdleu430 +Christina Tempelaar-Lietz ¨Christina Tempelaar-Lietz¨ +Christina Tempelaar-Lietz xlietz <31363633+xlietz@users.noreply.github.com> +Christopher Horvath +Christopher Kulla +Christoph Gohlke +cia-rana +Cristian Martínez +Dan Horák +Daniel Kaneider +Darby Johnston +Dave Sawyer +David Korczynski +Developer-Ecosystem-Engineering <65677710+Developer-Ecosystem-Engineering@users.noreply.github.com> +dgmzc +Diogo Teles Sant'Anna +Dirk Lemstra dirk +dracwyrm +Drew Hess +Ed Hanway Ed Hanway +Edward Kmett +Eric Sommerlade +Eric Wimmer +E Sommerlade +fgc +Florian Kainz +Grant Kim <6302240+enpinion@users.noreply.github.com> +Gregorio Litenstein +Gyula Gubacsi +Halfdan Ingvarsson +Harry Mallon +Huibean Luo +ianianda +Ibraheem Alhashim +Jack Kingsman +Jamie Kenyon +Jan Tojnar +jbradley +Jean-Francois Panisset <32653482+jfpanisset@users.noreply.github.com> +Jens Lindgren +Ji Hun Yu +Johannes Vollmer <32042925+johannesvollmer@users.noreply.github.com> +John Loy +John Mertic +Jonathan Stone +Jose Luis Cercos-Pita Jose Luis Cercós Pita +Joseph Goldstone Joseph Goldstone +Juha Reunanen +Julian Amann +Juri Abramov Juri Abramov +Juri Abramov JuriAbramov +Karl Hendrikse karlhendrikse +Karl Rasche Karl Rasche +Kevin Wheatley +Kimball Thurston +kwizart +Larry Gritz +Laurens Voerman +L. E. Segovia +Liam Fernandez +lilinjie +Lucy Wilkes +luzpaz +mancoast +mandree +Mark Reid +Mark Sisson <5761292+marksisson@users.noreply.github.com> +Martin Aumüller +Martin Husemann +Matthäus G. Chajdas +Matthias C. M. Troffaes +Matt Pharr +Md Sadman Chowdhury <61442059+SpicyCatGames@users.noreply.github.com> +Michael Thomas (malinka) +Nicholas Yue +Nick Porcino meshula +Nick Porcino Nick Porcino +Nick Porcino Nick Porcino +Nick Porcino nporcino-pixar <78001580+nporcino-pixar@users.noreply.github.com> +Nick Rasmussen Nick Rasmussen +Nick Rasmussen Nick Rasmussen +Nicolas Chauvet +Niklas Hambüchen +OgreTransporter +oleksii.vorobiov +Owen Thompson +patlefort +Paul Schneider +Peter Hillman peterhillman +Peter Hillman peterhillman +Peter Steneteg +Peter Urbanec +Phyrexian +Piotr Stanczyk Piotr +Piotr Stanczyk Piotr Stanczyk +Piotr Stanczyk pstanczyk +Ralph Potter +r-a-sattarov +Rémi Achard +Reto Kromer +Richard Goedeken +Sergey Fedorov +Shawn Walker-Salas +Simon Boorer +Simon Otter +Srinath Ravichandran +Thanh Ha +Thomas Debesse +Thorsten Kaufmann +Timothy Lyanguzov +Transporter +Vertexwahn +Wenzel Jakob +Wojciech Jarosz wjarosz +Xo Wang +Yaakov Selkowitz +Yining Karl Li +Yujie Shu Yujie Shu (Intern) +zengwei2000 <102871671+zengwei2000@users.noreply.github.com> diff --git a/BUILD.bazel b/BUILD.bazel index b51a6abe57..e233492d4b 100644 --- a/BUILD.bazel +++ b/BUILD.bazel @@ -257,7 +257,7 @@ cc_library( }), visibility = ["//visibility:public"], deps = [ - "@Imath", + "@imath//:Imath", "@libdeflate//:deflate", ], ) @@ -275,6 +275,7 @@ cc_library( "src/lib/OpenEXR/ImfChromaticities.cpp", "src/lib/OpenEXR/ImfChromaticitiesAttribute.cpp", "src/lib/OpenEXR/ImfCompositeDeepScanLine.cpp", + "src/lib/OpenEXR/ImfCompression.cpp", "src/lib/OpenEXR/ImfCompressionAttribute.cpp", "src/lib/OpenEXR/ImfCompressor.cpp", "src/lib/OpenEXR/ImfConvert.cpp", @@ -500,7 +501,7 @@ cc_library( deps = [ ":IlmThread", ":OpenEXRCore", - "@Imath", + "@imath//:Imath", ], ) diff --git a/CHANGES.md b/CHANGES.md index 5d52fd480b..99632d062a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,7 +3,10 @@ # OpenEXR Release Notes +* [Version 3.2.2](#version-322-february-11-2024) February 11, 2024 +* [Version 3.2.1](#version-321-september-27-2023) September 27, 2023 * [Version 3.2.0](#version-320-august-30-2023) August 30, 2023 +* [Version 3.1.12](#version-3112-february-11-2023) February 11, 2024 * [Version 3.1.11](#version-3111-august-13-2023) August 13, 2023 * [Version 3.1.10](#version-3110-august-2-2023) August 2, 2023 * [Version 3.1.9](#version-319-june-25-2023) June 25, 2023 @@ -23,6 +26,8 @@ * [Version 3.0.1](#version-301-april-1-2021) April 1, 2021 * [Version 3.0.1-beta](#version-301-beta-march-28-2021) March 28, 2021 * [Version 3.0.0-beta](#version-300-beta-march-16-2021) March 16, 2021 +* [Version 2.5.10](#version-2510-december-19-2023) December 19, 2023 +* [Version 2.5.9](#version-259-july-31-2023) July 31, 2023 * [Version 2.5.8](#version-258-march-18-2022) March 18, 2022 * [Version 2.5.7](#version-257-june-16-2021) June 16, 2021 * [Version 2.5.6](#version-256-may-17-2021) May 17, 2021 @@ -66,6 +71,75 @@ * [Version 1.0.1](#version-101) * [Version 1.0](#version-10) +## Version 3.2.2 (February 11, 2024) + +Patch release that addresses +[CVE-2023-5841](https://takeonme.org/cves/CVE-2023-5841.html). + +Note that this bug is present in the C++ API (since v3.1.0), although +it is in a routine that is predominantly used for development and +testing. It is not likely to appear in production code. + +This release also addresses: + +* OSS-fuzz [66491](https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=66491) +Out-of-memory in openexr_exrcorecheck_fuzzer +* OSS-fuzz [66489](https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=66489) +Null-dereference in `Imf_3_3::realloc_deepdata` + +### Merged Pull Requests + +* [1632](https://github.com/AcademySoftwareFoundation/openexr/pull/1632) +adjust checks for core to better match c++ checks +* [1630](https://github.com/AcademySoftwareFoundation/openexr/pull/1630) +fix issue with unpacking sample counts +* [1627](https://github.com/AcademySoftwareFoundation/openexr/pull/1627) +Fix CVE 2023 5841 + +## Version 3.2.1 (September 27, 2023) + +Patch release with miscellaneous build fixes: + +* Fix for linking statically against an external ``libdeflate`` +* Fix a compile error with ``OPENEXR_VERSION_HEX`` +* Fix various compiler warnings +* Pkg-config generation is now on by default for all systems, including Windows + +### Merged Pull Requests + +* [1568](https://github.com/AcademySoftwareFoundation/openexr/pull/1568) +Fix Imf/Iex/IlmThread namespaces in python bindings and website code +* [1565](https://github.com/AcademySoftwareFoundation/openexr/pull/1565) +Update openexr_deps.bzl +* [1562](https://github.com/AcademySoftwareFoundation/openexr/pull/1562) +Bazel: Improve module +* [1561](https://github.com/AcademySoftwareFoundation/openexr/pull/1561) +Clean up handling of libdeflate when linking static +* [1560](https://github.com/AcademySoftwareFoundation/openexr/pull/1560) +Omit OPENEXR_IMAGES_TAG from test image url if empty +* [1557](https://github.com/AcademySoftwareFoundation/openexr/pull/1557) +Set build-shared:OFF for Static build +* [1541](https://github.com/AcademySoftwareFoundation/openexr/pull/1541) +OPENEXR_INSTALL_PKG_CONFIG is on by default, even on Windows +* [1540](https://github.com/AcademySoftwareFoundation/openexr/pull/1540) +Default value for chromaticities attribute constructor in exrstdattr +* [1539](https://github.com/AcademySoftwareFoundation/openexr/pull/1539) +Fix OPENEXR_VERSION_HEX +* [1536](https://github.com/AcademySoftwareFoundation/openexr/pull/1536) +Python wheel setup gets version from OpenEXR.pc/Imath.pc +* [1534](https://github.com/AcademySoftwareFoundation/openexr/pull/1534) +Fix warnings from cross-compiling with x86_64-w64-mingw32-gcc-posix +* [1533](https://github.com/AcademySoftwareFoundation/openexr/pull/1533) +Fix warnings in multipartExamples.cpp +* [1532](https://github.com/AcademySoftwareFoundation/openexr/pull/1532) +Don't trigger ci/bazel/ossfuzz builds on pushes/PRs to src/wrappers +* [1531](https://github.com/AcademySoftwareFoundation/openexr/pull/1531) +Propagate OPENEXR_INSTALL_PKG_CONFIG to internal Imath +* [1530](https://github.com/AcademySoftwareFoundation/openexr/pull/1530) +Set minimal permissions for workflow python-wheels.yml +* [1528](https://github.com/AcademySoftwareFoundation/openexr/pull/1528) +Remove check for _MSC_VER in internal_cpuid.h + ## Version 3.2.0 (August 30, 2023) Minor release with several additions, changes and improvements: @@ -187,6 +261,12 @@ Direct-leak in ``Imf_3_1::RgbaInputFile::RgbaInputFile`` ### Merged Pull Requests +* [1527](https://github.com/AcademySoftwareFoundation/openexr/pull/1527) +`OpenEXRConfig.h.in` uses version extracted from `openexr_version.h` +* [1525](https://github.com/AcademySoftwareFoundation/openexr/pull/1525) +Add bzlmod support +* [1523](https://github.com/AcademySoftwareFoundation/openexr/pull/1523) +Add `DEPENDENCIES Imath::Imath` for OpenEXRCore (#1523) * [1522](https://github.com/AcademySoftwareFoundation/openexr/pull/1522) Rename cifuzz workflow to OSS-Fuzz, and filter out unnecessary triggers * [1520](https://github.com/AcademySoftwareFoundation/openexr/pull/1520) @@ -390,6 +470,30 @@ Improve Bazel Build * [1058](https://github.com/AcademySoftwareFoundation/openexr/pull/1058) Add ``validate_openexr_libs.sh`` to validate .so symlinks +## Version 3.1.12 (February 11, 2024) + +Patch release that addresses +[CVE-2023-5841](https://takeonme.org/cves/CVE-2023-5841.html). + +Note that this bug is present in the C++ API (since v3.1.0), although +it is in a routine that is predominantly used for development and +testing. It is not likely to appear in production code. +This release also addresses: + +* OSS-fuzz [66491](https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=66491) +Out-of-memory in openexr_exrcorecheck_fuzzer +* OSS-fuzz [66489](https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=66489) +Null-dereference in `Imf_3_3::realloc_deepdata` + +### Merged Pull Requests + +* [1632](https://github.com/AcademySoftwareFoundation/openexr/pull/1632) +adjust checks for core to better match c++ checks +* [1630](https://github.com/AcademySoftwareFoundation/openexr/pull/1630) +fix issue with unpacking sample counts +* [1627](https://github.com/AcademySoftwareFoundation/openexr/pull/1627) +Fix CVE 2023 5841 + ## Version 3.1.11 (August 13, 2023) Patch release that fixes a build failure with ``-march=x86-64-v3`` @@ -1379,6 +1483,28 @@ Specific OSS-fuzz issues addressed include: * [791](https://github.com/AcademySoftwareFoundation/openexr/pull/791) Initial removal of all Imath source files and minimal cmake adjustments * [769](https://github.com/AcademySoftwareFoundation/openexr/pull/769) Bugfix/arkellr remove cvsignore files +## Version 2.5.10 (December 19, 2023) + +Patch release that fixes a build failure on macOS prior to 10.6 +(fallback for missing `libdispatch`). + +### Merged Pull Requests + +* [1596] (https://github.com/AcademySoftwareFoundation/openexr/pull/1596) +macOS: use libdispatch only where available + +## Version 2.5.9 (July 31, 2023) + +Patch release that fixes a compile failure with gcc-13 gcc 13 and +problem with PyIlmBase's pkgconfig. + +### Merged Pull Requests + +* [1499](https://github.com/AcademySoftwareFoundation/openexr/pull/1499) +fix build of 2.5 branch with GCC 13 +* [1253](https://github.com/AcademySoftwareFoundation/openexr/pull/1253) +Adjust exec_prefix path for PyIlmBase's pkgconfig file + ## Version 2.5.8 (March 18, 2022) Patch release that backports two fixes: @@ -4346,18 +4472,15 @@ This is a relatively minor update to the project, with the following changes: film frames. (Florian Kainz) -* Removed #include from ImfAttribute.h, ImfHeader.h - and ImfXdr.h so that including header files such as - ImfInputFile.h no longer defines ASSERT and THROW macros, - which may conflict with similar macros defined by - application programs. - (Florian Kainz) +* Removed #include from ImfAttribute.h, ImfHeader.h and + ImfXdr.h so that including header files such as ImfInputFile.h no + longer defines ASSERT and THROW macros, which may conflict with + similar macros defined by application programs. (Florian Kainz) -* Converted HTML documentation to OpenOffice format to - make maintaining the documents easier: - api.html -> ReadingAndWritingImageFiles.sxw - details.html -> TechnicalIntroduction.sxw - (Florian Kainz) +* Converted HTML documentation to OpenOffice format to make + maintaining the documents easier: api.html -> + ReadingAndWritingImageFiles.sxw details.html -> + TechnicalIntroduction.sxw (Florian Kainz) ## Version 1.2.1 (June 6, 2004) @@ -4367,7 +4490,8 @@ changes: * reduced memory footprint of exrenvmap and exrmaketiled utilities. -* IlmImf: new helper functions to determine whether a file is an OpenEXR file, and whether it's scanline- or tile-based. +* IlmImf: new helper functions to determine whether a file is an + OpenEXR file, and whether it's scanline- or tile-based. * IlmImf: bug fix for PXR24 compression with ySampling != 1. @@ -4388,23 +4512,22 @@ changes: description of preview images and environment maps to docs/api.html (Florian Kainz) -* Bug fix: PXR24 compression did not work properly for channels - with ySampling != 1. - (Florian Kainz) +* Bug fix: PXR24 compression did not work properly for channels with + ySampling != 1. (Florian Kainz) -* Made ``template `` become ``template `` for - the ``transform(ObjectS, ObjectT)`` methods. This was done to allow - for differing templated objects to be passed in e.g. say a +* Made ``template `` become ``template `` + for the ``transform(ObjectS, ObjectT)`` methods. This was done to + allow for differing templated objects to be passed in e.g. say a ``Box>`` and a ``Matrix44``, where S=float and T=double. (Jeff Yost, Arkell Rasiah) -* New method Matrix44::setTheMatrix(). Used for assigning a - M44f to a M44d. (Jeff Yost, Arkell Rasiah) +* New method Matrix44::setTheMatrix(). Used for assigning a M44f to a + M44d. (Jeff Yost, Arkell Rasiah) -* Added convenience Color typedefs for half versions of Color3 - and Color4. Note the Makefile.am for both Imath and ImathTest - have been updated with -I and/or -L pathing to Half. - (Max Chen, Arkell Rasiah) +* Added convenience Color typedefs for half versions of Color3 and + Color4. Note the Makefile.am for both Imath and ImathTest have been + updated with -I and/or -L pathing to Half. (Max Chen, Arkell + Rasiah) * Methods equalWithAbsError() and equalWithRelError() are now declared as const. (Colette Mullenhoff, Arkell Rasiah) @@ -4415,14 +4538,13 @@ changes: * Added Custom low-level file I/O examples to IlmImfExamples and to the docs/api.html document. (Florian Kainz) -* Eliminated most warnings messages when OpenEXR is compiled - with Visual C++. The OpenEXR code uses lots of (intentional - and unintended) implicit type conversions. By default, Visual - C++ warns about almost all of them. Most implicit conversions - have been removed from the .h files, so that including them - should not generate warnings even at warning level 3. Most - .cpp files are now compiled with warning level 1. - (Florian Kainz) +* Eliminated most warnings messages when OpenEXR is compiled with + Visual C++. The OpenEXR code uses lots of (intentional and + unintended) implicit type conversions. By default, Visual C++ warns + about almost all of them. Most implicit conversions have been + removed from the .h files, so that including them should not + generate warnings even at warning level 3. Most .cpp files are now + compiled with warning level 1. (Florian Kainz) ## Version 1.2.0 (May 11, 2004) @@ -4502,59 +4624,56 @@ format. ### Detailed Changes: -* Half: operator= and variants now return by reference rather - than by value. This brings half into conformance with - built-in types. (Drew Hess) +* Half: operator= and variants now return by reference rather than by + value. This brings half into conformance with built-in types. + (Drew Hess) -* Half: remove copy constructor, let compiler supply its - own. This improves performance up to 25% on some - expressions using half. (Drew Hess) +* Half: remove copy constructor, let compiler supply its own. This + improves performance up to 25% on some expressions using half. + (Drew Hess) -* configure: don't try to be fancy with CXXFLAGS, just use - what the user supplies or let configure choose a sensible - default if CXXFLAGS is not defined. +* configure: don't try to be fancy with CXXFLAGS, just use what the + user supplies or let configure choose a sensible default if CXXFLAGS + is not defined. * IlmImf: fixed a bug in reading scanline files on big-endian architectures. (Drew Hess) -* exrmaketiled: Added an option to select compression type. - (Florian Kainz) +* exrmaketiled: Added an option to select compression type. (Florian + Kainz) -* exrenvmap: Added an option to select compression type. - (Florian Kainz) +* exrenvmap: Added an option to select compression type. (Florian + Kainz) * exrdisplay: Added some new command-line options. (Florian Kainz) -* IlmImf: Added Pixar's new "slightly lossy" image compression - method. The new method, named PXR24, preserves HALF and - UINT data without loss, but FLOAT pixels are converted to - a 24-bit representation. PXR24 appears to compress - FLOAT depth buffers very well without losing much accuracy. - (Loren Carpenter, Florian Kainz) - -* Changed top-level LICENSE file to allow for other copyright - holders for individual files. - -* IlmImf: TILED FILE FORMAT CHANGE. TiledOutputFile was - incorrectly interleaving channels and scanlines before - passing pixel data to a compressor. The lossless compressors - still work, but lossy compressors do not. Fix the bug by - interleaving channels and scanlines in tiled files in the - same way as ScanLineOutputFile does. Programs compiled with - the new version of IlmImf cannot read tiled images produced - with version 1.1.0. (Florian Kainz) +* IlmImf: Added Pixar's new "slightly lossy" image compression method. + The new method, named PXR24, preserves HALF and UINT data without + loss, but FLOAT pixels are converted to a 24-bit representation. + PXR24 appears to compress FLOAT depth buffers very well without + losing much accuracy. (Loren Carpenter, Florian Kainz) + +* Changed top-level LICENSE file to allow for other copyright holders + for individual files. + +* IlmImf: TILED FILE FORMAT CHANGE. TiledOutputFile was incorrectly + interleaving channels and scanlines before passing pixel data to a + compressor. The lossless compressors still work, but lossy + compressors do not. Fix the bug by interleaving channels and + scanlines in tiled files in the same way as ScanLineOutputFile does. + Programs compiled with the new version of IlmImf cannot read tiled + images produced with version 1.1.0. (Florian Kainz) * IlmImf: ImfXdr.h fix for 64-bit architectures. (Florian Kainz) -* IlmImf: OpenEXR now supports YCA (luminance/chroma/alpha) - images with subsampled chroma channels. When an image - is written with the RGBA convenience interface, selecting - WRITE_YCA instead of WRITE_RGBA causes the library to - convert the pixels to YCA format. If WRITE_Y is selected, - only luminance is stored in the file (for black and white - images). When an image file is read with the RGBA convenience - interface, YCA data are automatically converted back to RGBA. - (Florian Kainz) +* IlmImf: OpenEXR now supports YCA (luminance/chroma/alpha) images + with subsampled chroma channels. When an image is written with the + RGBA convenience interface, selecting WRITE_YCA instead of + WRITE_RGBA causes the library to convert the pixels to YCA format. + If WRITE_Y is selected, only luminance is stored in the file (for + black and white images). When an image file is read with the RGBA + convenience interface, YCA data are automatically converted back to + RGBA. (Florian Kainz) * IlmImf: speed up reading tiled files as scan lines. (Florian Kainz) @@ -4708,9 +4827,9 @@ we're working to restore them. * Fixes for Visual Studio .NET 2003 w/ Microsoft C++ compiler. (Various) -* Random Imath fixes and enhancements. Note that - extractSHRT now takes an additional optional - argument, see ImathMatrixAlgo.h for details. (Various) +* Random Imath fixes and enhancements. Note that extractSHRT now + takes an additional optional argument, see ImathMatrixAlgo.h for + details. (Various) * Added Wojciech Jarosz to AUTHORS file. @@ -4759,9 +4878,9 @@ the source code. * Add an IlmImfDll project to the Visual Studio 6.0 workspace. -* In Win32, export the ImfCRgbaFile C interface via a DLL so - that Visual C++ 6.0 users can link against an Intel-compiled - IlmImf. (Andreas Kahler) +* In Win32, export the ImfCRgbaFile C interface via a DLL so that + Visual C++ 6.0 users can link against an Intel-compiled IlmImf. + (Andreas Kahler) * Use auto_ptr in ImfAutoArray on Win32, it doesn't like large automatic stacks. @@ -4842,15 +4961,14 @@ the source code. * Added new FP predecessor/successor functions to Imath, added tests to ImathTest -* Fixed a bug in Imath::extractSHRT for 3x3 matricies when - exactly one of the original scaling factors is negative, updated - ImathTest to check this case. +* Fixed a bug in Imath::extractSHRT for 3x3 matricies when exactly one + of the original scaling factors is negative, updated ImathTest to + check this case. * Install include files when 'make install' is run. -* exrdisplay requires fltk 1.1+ now in an effort to support - a MacOS X display program (fltk 1.1 runs on OS X), though this - is untested. +* exrdisplay requires fltk 1.1+ now in an effort to support a MacOS X + display program (fltk 1.1 runs on OS X), though this is untested. * renamed configure.in to configure.ac @@ -4858,9 +4976,8 @@ the source code. * Removed ImfHalfXdr.h, it's not used anymore. -* Revamped the autoconf system, added some compile-time - optimizations, a pkgconfig target, and some maintainer-specific - stuff. +* Revamped the autoconf system, added some compile-time optimizations, + a pkgconfig target, and some maintainer-specific stuff. ## Version 1.0.2 diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a97330051..9a773486bb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,7 +2,7 @@ # Copyright (c) Contributors to the OpenEXR Project. # We require this to get object library link library support -cmake_minimum_required(VERSION 3.12) +cmake_minimum_required(VERSION 3.14) if(POLICY CMP0074) # enable find_package() to use _ROOT as a hint @@ -48,8 +48,35 @@ set(OPENEXR_LIB_VERSION "${OPENEXR_LIB_SOVERSION}.${OPENEXR_VERSION}") # e.g. "3 option(OPENEXR_INSTALL "Install OpenEXR libraries" ON) option(OPENEXR_INSTALL_TOOLS "Install OpenEXR tools" ON) -if(OPENEXR_INSTALL_TOOLS AND NOT OPENEXR_INSTALL) - message(SEND_ERROR "OPENEXR_INSTALL_TOOLS requires OPENEXR_INSTALL") + +# uninstall target +if(NOT TARGET uninstall) + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" + IMMEDIATE @ONLY) + add_custom_target(uninstall + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) +endif() + +# uninstall target +if(NOT TARGET uninstall) + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" + IMMEDIATE @ONLY) + add_custom_target(uninstall + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) +endif() + +# uninstall target +if(NOT TARGET uninstall) + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake" + IMMEDIATE @ONLY) + add_custom_target(uninstall + COMMAND ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake) endif() include(cmake/LibraryDefine.cmake) @@ -73,8 +100,13 @@ if(BUILD_TESTING AND NOT OPENEXR_IS_SUBPROJECT) endif() # Include these two modules without enable/disable options -add_subdirectory(src/lib) -add_subdirectory(src/bin) +if (OPENEXR_BUILD_LIBS) + add_subdirectory(src/lib) +endif() + +if(OPENEXR_BUILD_TOOLS AND OPENEXR_BUILD_LIBS) + add_subdirectory(src/bin) +endif() # Tell CMake where to find the OpenEXRConfig.cmake file. Makes it possible to call # find_package(OpenEXR) in downstream projects @@ -83,8 +115,7 @@ set(OpenEXR_DIR "${CMAKE_CURRENT_BINARY_DIR}/cmake" CACHE PATH "" FORCE) # Can be empty since we already defined the targets in add_subdirectory file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/cmake/OpenEXRTargets.cmake" "# Dummy file") -option(OPENEXR_INSTALL_EXAMPLES "Install OpenEXR examples" ON) -if(OPENEXR_INSTALL_EXAMPLES) +if(OPENEXR_BUILD_EXAMPLES AND OPENEXR_BUILD_LIBS) add_subdirectory( src/examples ) endif() @@ -112,7 +143,7 @@ endif() #set(CTEST_DROP_SITE_CDASH TRUE) include(CTest) -if(BUILD_TESTING AND NOT OPENEXR_IS_SUBPROJECT) +if(BUILD_TESTING AND OPENEXR_BUILD_LIBS AND NOT OPENEXR_IS_SUBPROJECT) add_subdirectory(src/test) endif() @@ -132,12 +163,11 @@ if (BUILD_WEBSITE AND NOT OPENEXR_IS_SUBPROJECT) add_subdirectory(website) endif() -if (NOT OPENEXR_IS_SUBPROJECT) +if (OPENEXR_BUILD_LIBS AND NOT OPENEXR_IS_SUBPROJECT) # Even if not building the website, still make sure the website example code compiles. add_subdirectory(website/src) endif() -option(OPENEXR_BUILD_PYTHON "Set ON to build python bindings") -if (OPENEXR_BUILD_PYTHON AND NOT OPENEXR_IS_SUBPROJECT) +if (OPENEXR_BUILD_PYTHON AND OPENEXR_BUILD_LIBS AND NOT OPENEXR_IS_SUBPROJECT) add_subdirectory(src/wrappers/python) endif() diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9a9ba9cf65..09a3c3ac62 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -211,7 +211,7 @@ openexr-dev@lists.aswf.io mail list. ### Pull Requests -Contributions should be submitted as Github pull requests. See +Contributions should be submitted as GitHub pull requests. See [Creating a pull request](https://help.github.com/articles/creating-a-pull-request/) if you're unfamiliar with this concept. @@ -227,7 +227,7 @@ with a separate pull request. 3. Push commits to your fork. -4. Create a Github pull request from your topic branch. +4. Create a GitHub pull request from your topic branch. 5. Pull requests will be reviewed by project committers and contributors, who may discuss, offer constructive feedback, request changes, or approve @@ -591,14 +591,26 @@ The preferred workflow is: d. Send an email update to ``openexr-dev@lists.aswf.io`` notifying the community of the addition and the new tag. -7. Publish the release +7. Create a signed release tag + + a. Make sure you have a [GPG + key](https://docs.github.com/en/authentication/managing-commit-signature-verification/generating-a-new-gpg-key) + and it is + [registered](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key) + with your GitHub account and git config. + + b. Create a signed tag with the release name via `git tag -s v3.1.9`. + + c. Push the tag via `git push --tags` + +8. Publish the release a. Click the "Publish release" button on the GitHub release draft b. Send an email to ``openexr-dev@lists.aswf.io`` officially annoucing the release. -8. Update the ``release`` branch, which should always point to the +9. Update the ``release`` branch, which should always point to the most recent patch of the most recent minor release, i.e. the most preferred release. @@ -608,24 +620,24 @@ The preferred workflow is: % git merge RB-3.1 % git push -9. Submit a PR that adds the release notes to [CHANGES.md](CHANGES.md) - on the main branch. Cherry-pick the release notes commit from - the release branch. +10. Submit a PR that adds the release notes to [CHANGES.md](CHANGES.md) + on the main branch. Cherry-pick the release notes commit from + the release branch. - - If any changes have gone into [SECURITY.md](SECURITY), cherry-pick - the associated commit as well. + - If any changes have gone into [SECURITY.md](SECURITY), cherry-pick + the associated commit as well. - - Also include in this PR edits to [``docs/news.rst``](docs/news.rst) - that add an announcment of the release. + - Also include in this PR edits to [``docs/news.rst``](docs/news.rst) + that add an announcment of the release. -10. After review/merge of the updates to ``docs/news.rst``, build the +11. After review/merge of the updates to ``docs/news.rst``, build the website at https://readthedocs.org/projects/openexr. -11. If the release has resolved any OSS-Fuzz issues, update the +12. If the release has resolved any OSS-Fuzz issues, update the associated pages at https://bugs.chromium.org/p/oss-fuzz with a reference to the release. -12. If the release has resolved any public CVE's, request an update +13. If the release has resolved any public CVE's, request an update from the registry service providing the release and a link to the release notes. diff --git a/CONTRIBUTORS.md b/CONTRIBUTORS.md index 9fde40b23f..3178a815d4 100644 --- a/CONTRIBUTORS.md +++ b/CONTRIBUTORS.md @@ -4,20 +4,37 @@ This is a list of contributors to the OpenEXR project, sorted alphabetically by first name. -If you know of missing, please email: info@openexr.com. - +If you know of missing, please email info@openexr.com or submit a PR. + +* Aaron Demolder +* Abe Fettig * Aloys Baillet * Andre Mazzone * Andrew Kunz +* Anton Dukhovnikov +* Antonio Rojas +* Aras Pranckevičius * Arkady Shapkin * Arkell Rasiah +* Axel Waggershauser +* Balázs Oroszi +* Barnaby Robson +* Ben Grimes * Brendan Bolles * Cary Phillips +* Chris Leu * Christina Tempelaar-Lietz * Christopher Horvath * Christopher Kulla +* Christoph Gohlke +* Cristian Martínez +* Dan Horák * Daniel Kaneider * Darby Johnston +* Dave Sawyer +* David Korczynski +* Diogo Teles Sant'Anna +* Dirk Lemstra * Drew Hess * Ed Hanway * Edward Kmett @@ -25,47 +42,75 @@ If you know of missing, please email: info@openexr.com. * Eric Wimmer * E Sommerlade * Florian Kainz +* Grant Kim +* Gregorio Litenstein +* Gyula Gubacsi * Halfdan Ingvarsson * Harry Mallon +* Huibean Luo * Ibraheem Alhashim * Jack Kingsman * Jamie Kenyon * Jan Tojnar +* Jean-Francois Panisset +* Jens Lindgren * Ji Hun Yu +* Johannes Vollmer * John Loy * John Mertic * Jonathan Stone +* Jose Luis Cercos-Pita +* Joseph Goldstone +* Juha Reunanen * Julian Amann * Juri Abramov +* Karl Hendrikse * Karl Rasche * Kevin Wheatley * Kimball Thurston * Larry Gritz +* Laurens Voerman +* L. E. Segovia * Liam Fernandez * Lucy Wilkes +* Mark Reid +* Mark Sisson +* Martin Aumüller +* Martin Husemann * Matthäus G. Chajdas +* Matthias C. M. Troffaes +* Matt Pharr +* Md Sadman Chowdhury * Michael Thomas * Nicholas Yue * Nick Porcino * Nick Rasmussen * Nicolas Chauvet * Niklas Hambüchen +* OgreTransporter * Owen Thompson * Paul Schneider * Peter Hillman * Peter Steneteg +* Peter Urbanec * Phil Barrett * Piotr Stanczyk * Ralph Potter +* Rémi Achard * Reto Kromer * Richard Goedeken +* Sergey Fedorov * Shawn Walker-Salas +* Simon Boorer * Simon Otter * Srinath Ravichandran * Thanh Ha +* Thomas Debesse * Thorsten Kaufmann +* Timothy Lyanguzov * Wenzel Jakob * Wojciech Jarosz * Xo Wang +* Yaakov Selkowitz * Yining Karl Li * Yujie Shu diff --git a/MODULE.bazel b/MODULE.bazel index ca3ad9860c..8742e50fd6 100644 --- a/MODULE.bazel +++ b/MODULE.bazel @@ -6,7 +6,7 @@ module( compatibility_level = 1, ) -bazel_dep(name = "bazel_skylib", version = "1.4.2") -bazel_dep(name = "imath", repo_name = "Imath", version = "3.1.9") +bazel_dep(name = "bazel_skylib", version = "1.5.0") +bazel_dep(name = "imath", version = "3.1.11") bazel_dep(name = "libdeflate", version = "1.19") -bazel_dep(name = "platforms", version = "0.0.7") +bazel_dep(name = "platforms", version = "0.0.8") diff --git a/README.md b/README.md index d41b6a19d7..dfa8f8660b 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ for more information. # Quick Start See the [technical documentation](https://openexr.readthedocs.io) for -complete details, but to get started, the "hello, world" `.exr` writer program is: +complete details, but to get started, the "Hello, world" [`exrwriter.cpp`](https://raw.githubusercontent.com/AcademySoftwareFoundation/openexr/main/website/src/exrwriter/exrwriter.cpp) writer program is: #include #include @@ -87,18 +87,18 @@ complete details, but to get started, the "hello, world" `.exr` writer program i return 0; } -The `CMakeLists.txt` to build: +The [`CMakeLists.txt`](https://raw.githubusercontent.com/AcademySoftwareFoundation/openexr/main/website/src/exrwriter/CMakeLists.txt) to build: - cmake_minimum_required(VERSION 3.10) + cmake_minimum_required(VERSION 3.12) project(exrwriter) find_package(OpenEXR REQUIRED) - add_executable(${PROJECT_NAME} writer.cpp) + add_executable(${PROJECT_NAME} exrwriter.cpp) target_link_libraries(${PROJECT_NAME} OpenEXR::OpenEXR) To build: - $ cmake -S . -B _build + $ cmake -S . -B _build -DCMAKE_PREFIX_PATH= $ cmake --build _build For more details, see [The OpenEXR @@ -119,6 +119,8 @@ API](https://openexr.readthedocs.io/en/latest/API.html#the-openexr-api). - Calendar: https://lists.aswf.io/g/openexr-dev/calendar + - Meeting Notes: https://wiki.aswf.io/display/OEXR/TSC+Meetings + * **Report a bug:** - Submit an Issue: https://github.com/AcademySoftwareFoundation/openexr/issues diff --git a/SECURITY.md b/SECURITY.md index e9686c6ab2..37e8186c53 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,14 +6,15 @@ ## Reporting a Vulnerability If you think you've found a potential vulnerability in OpenEXR, please -report it by emailing security@openexr.com. Only Technical Steering -Committee members and Academy Software Foundation project management -have access to these messages. Include detailed steps to reproduce the -issue, and any other information that could aid an investigation. Our -policy is to respond to vulnerability reports within 14 days. +report it by filing a GitHub [security +advisory](https://github.com/AcademySoftwareFoundation/openexr/security/advisories/new). Alternatively, +email security@openexr.com and provide your contact info for further +private/secure discussion. If your email does not receive a prompt +acknowledgement, your address may be blocked. -Our policy is to address critical security vulnerabilities rapidly and -post patches as quickly as possible. +Our policy is to acknowledge the receipt of vulnerability reports +within 48 hours. Our policy is to address critical security vulnerabilities +rapidly and post patches within 14 days if possible. ## Known Vulnerabilities @@ -57,3 +58,119 @@ These vulnerabilities are present in the given versions: See the [release notes](CHANGES.md) for more information. +## Supported Versions + +This gives guidance about which branches are supported with patches to +security vulnerabilities. + +| Version / branch | Supported | +| --------- | ---------------------------------------------------- | +| main | :white_check_mark: :construction: ALL fixes immediately, but this is a branch under development with a frequently unstable ABI and occasionally unstable API. | +| 3.2.x | :white_check_mark: All fixes that can be backported without breaking ABI compatibility. | +| 3.1.x | :warning: Only the most critical fixes, only if they can be easily backported. | +| 3.0.x | :warning: Only the most critical fixes, only if they can be easily backported. | +| 2.5.x | :warning: Only the most critical fixes, only if they can be easily backported. | +| <= 1.x | :x: No longer receiving patches of any kind. | + +## Signed Releases + +Releases artifacts are signed via +[sigstore](https://www.sigstore.dev). See +[release-sign.yml](.github/workflows/release-sign.yml) for details. + +To verify a downloaded release at a given tag: + + % pip install sigstore + % sigstore verify github --cert-identity https://github.com/AcademySoftwareFoundation/openexr/.github/workflows/release-sign.yml@refs/tags/ openexr-.tar.gz + +## Security Expectations + +### Software Features + +- The OpenEXR project implements the EXR image file format, used + throughout the motion picture industry and beyond, on Linux, macOS, + and Windows. + +- The project consists of a software run-time library, implemented in + C/C++ and built via cmake, that reads and writes image data + files. The project also distributes python wrappings for the C/C++ + I/O API. + +- The library reads and writes binary image data and text-based + metadata, treated as blind data, none of which is executable code. + +- Other than the website and online technical documentation, the + project implements no web/online services or network communication + protocols. The library never requests any security or + authentication credentials or login information from users. + + The website implements no interactive features and requires no login + credentials. + +- The library reads and writes only to file paths specificly requested + via the C/C++ API. The runtime library uses no system configuration + files or sidecar data files. Access to data files uses only standard + file I/O system calls. + +- The library compresses/decompresses data via standard compression + algorithms but uses no cryptographic or confidentiality protocols. + +### Software Dependencies + +OpenEXR depends on +[Imath](https://github.com/AcademySoftwareFoundation/Imath), a library +of basic math operations also maintained and distributed by the +OpenEXR project. Imath follows the same security conventions +documented here for OpenEXR itself. The core Imath library has no +external dependencies. The Imath python bindings depend on python and +boost. + +The only +external library dependency of OpenEXR is +[libdeflate](https://github.com/ebiggers/libdeflate), which implements +standard deflate/zlib/gzip compression and decompression. + +The project uses +[Snyk](https://github.com/AcademySoftwareFoundation/openexr/blob/main/.github/workflows/snyk-scan-pr.yml) +to scan for dependency vulnerability. + +### Potential Vulnerabilities + +Potential entry points are images being loaded using the +library. Malformed images could caused issues such as heap buffer +overflows, out-of-memory faults, or segmentation faults that could be +exploitable as denial-of-service attacks. + +### Hardening + +#### Testing + +The OpenEXR project implements a comprehensive suite of validation +tests, including fuzz testing to harden against malicious input +data. Note that fuzz testing hardens only against *small* input data +files and is not a comprehensive test against all potential input. + +Note that the +[exrcheck](https://github.com/AcademySoftwareFoundation/openexr/tree/main/src/bin/exrcheck) +utility is intended to be used by testers to demonstrate a particular +proof-of-concept input file exposes a vulnerability, and it is very +helpful to let us know if a vulnerability can be reproduced using that +tool. + +The project also uses the [OSS +Fuzz](https://bugs.chromium.org/p/oss-fuzz) service for continuous +fuzz testing. + +#### Development Cycle and Distribution + +OpenEXR is downloadable and buildable by C/C++ source via GitHub. Only +members of the project's Technical Steering Committee, all veteran +software engineers at major motion picture studios or vendors, have +write permissions on the source code repository. All critical software +changes are reviewed by multiple TSC members. + +The library is distributed in binary form via many common package +managers across all platforms. + + + diff --git a/WORKSPACE.bazel b/WORKSPACE.bazel index 4c12980daf..b62f912e42 100644 --- a/WORKSPACE.bazel +++ b/WORKSPACE.bazel @@ -1,12 +1,4 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright Contributors to the OpenEXR Project. -workspace(name = "com_openexr") - -load("//:bazel/third_party/openexr_deps.bzl", "openexr_deps") - -openexr_deps() - -load("@bazel_skylib//:workspace.bzl", "bazel_skylib_workspace") - -bazel_skylib_workspace() +# WORKSPACE marker file needed by Bazel diff --git a/bazel/third_party/Imath.BUILD b/bazel/third_party/Imath.BUILD deleted file mode 100644 index 52b1853b82..0000000000 --- a/bazel/third_party/Imath.BUILD +++ /dev/null @@ -1,76 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) Contributors to the OpenEXR Project. - -load("@bazel_skylib//rules:expand_template.bzl", "expand_template") - -expand_template( - name = "ImathConfig", - out = "src/Imath/ImathConfig.h", - substitutions = { - "@IMATH_INTERNAL_NAMESPACE@": "Imath_3_1", - "@IMATH_LIB_VERSION@": "3.1.9", - "@IMATH_NAMESPACE_CUSTOM@": "0", - "@IMATH_NAMESPACE@": "Imath", - "@IMATH_PACKAGE_NAME@": "Imath 3.1.9", - "@Imath_VERSION_MAJOR@": "3", - "@Imath_VERSION_MINOR@": "1", - "@Imath_VERSION_PATCH@": "9", - "@IMATH_VERSION@": "3.1.9", - "#cmakedefine IMATH_HALF_USE_LOOKUP_TABLE": "#define IMATH_HALF_USE_LOOKUP_TABLE", - "#cmakedefine IMATH_ENABLE_API_VISIBILITY": "#define IMATH_ENABLE_API_VISIBILITY", - "#cmakedefine IMATH_HAVE_LARGE_STACK": "/* #undef IMATH_HAVE_LARGE_STACK */", - "#cmakedefine01 IMATH_USE_NOEXCEPT": "#define IMATH_USE_NOEXCEPT 1", - }, - template = "config/ImathConfig.h.in", -) - -cc_library( - name = "Imath", - srcs = [ - "src/Imath/ImathColorAlgo.cpp", - "src/Imath/ImathFun.cpp", - "src/Imath/ImathMatrixAlgo.cpp", - "src/Imath/ImathRandom.cpp", - "src/Imath/half.cpp", - "src/Imath/toFloat.h", - ], - hdrs = [ - "src/Imath/ImathBox.h", - "src/Imath/ImathBoxAlgo.h", - "src/Imath/ImathColor.h", - "src/Imath/ImathColorAlgo.h", - "src/Imath/ImathConfig.h", - "src/Imath/ImathEuler.h", - "src/Imath/ImathExport.h", - "src/Imath/ImathForward.h", - "src/Imath/ImathFrame.h", - "src/Imath/ImathFrustum.h", - "src/Imath/ImathFrustumTest.h", - "src/Imath/ImathFun.h", - "src/Imath/ImathGL.h", - "src/Imath/ImathGLU.h", - "src/Imath/ImathInt64.h", - "src/Imath/ImathInterval.h", - "src/Imath/ImathLine.h", - "src/Imath/ImathLineAlgo.h", - "src/Imath/ImathMath.h", - "src/Imath/ImathMatrix.h", - "src/Imath/ImathMatrixAlgo.h", - "src/Imath/ImathNamespace.h", - "src/Imath/ImathPlane.h", - "src/Imath/ImathPlatform.h", - "src/Imath/ImathQuat.h", - "src/Imath/ImathRandom.h", - "src/Imath/ImathRoots.h", - "src/Imath/ImathShear.h", - "src/Imath/ImathSphere.h", - "src/Imath/ImathTypeTraits.h", - "src/Imath/ImathVec.h", - "src/Imath/ImathVecAlgo.h", - "src/Imath/half.h", - "src/Imath/halfFunction.h", - "src/Imath/halfLimits.h", - ], - includes = ["src/Imath"], - visibility = ["//visibility:public"], -) diff --git a/bazel/third_party/libdeflate.BUILD b/bazel/third_party/libdeflate.BUILD deleted file mode 100644 index 0159b57d9b..0000000000 --- a/bazel/third_party/libdeflate.BUILD +++ /dev/null @@ -1,55 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) Contributors to the OpenEXR Project. - -cc_library( - name = "deflate", - srcs = [ - "common_defs.h", - "lib/adler32.c", - "lib/adler32_vec_template.h", - "lib/arm/adler32_impl.h", - "lib/arm/cpu_features.c", - "lib/arm/cpu_features.h", - "lib/arm/crc32_impl.h", - "lib/arm/crc32_pmull_helpers.h", - "lib/arm/crc32_pmull_wide.h", - "lib/arm/matchfinder_impl.h", - "lib/bt_matchfinder.h", - "lib/cpu_features_common.h", - "lib/crc32.c", - "lib/crc32_multipliers.h", - "lib/crc32_tables.h", - "lib/decompress_template.h", - "lib/deflate_compress.c", - "lib/deflate_compress.h", - "lib/deflate_constants.h", - "lib/deflate_decompress.c", - "lib/gzip_compress.c", - "lib/gzip_constants.h", - "lib/gzip_decompress.c", - "lib/hc_matchfinder.h", - "lib/ht_matchfinder.h", - "lib/lib_common.h", - "lib/matchfinder_common.h", - "lib/utils.c", - "lib/x86/adler32_impl.h", - "lib/x86/cpu_features.c", - "lib/x86/cpu_features.h", - "lib/x86/crc32_impl.h", - "lib/x86/crc32_pclmul_template.h", - "lib/x86/decompress_impl.h", - "lib/x86/matchfinder_impl.h", - "lib/zlib_compress.c", - "lib/zlib_constants.h", - "lib/zlib_decompress.c", - ], - hdrs = ["libdeflate.h"], - includes = ["."], - visibility = ["//visibility:public"], -) - -alias( - name = "libdeflate", - actual = ":deflate", - visibility = ["//visibility:public"], -) diff --git a/bazel/third_party/openexr_deps.bzl b/bazel/third_party/openexr_deps.bzl deleted file mode 100644 index c0833d1c48..0000000000 --- a/bazel/third_party/openexr_deps.bzl +++ /dev/null @@ -1,38 +0,0 @@ -# SPDX-License-Identifier: BSD-3-Clause -# Copyright (c) Contributors to the OpenEXR Project. - -"""External dependencies for openexr.""" - -load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") -load("@bazel_tools//tools/build_defs/repo:utils.bzl", "maybe") - -def openexr_deps(): - """Fetches dependencies (libdeflate and Imath) of OpenEXR and Skylib for header generation.""" - - maybe( - http_archive, - name = "libdeflate", - build_file = "@com_openexr//:bazel/third_party/libdeflate.BUILD", - sha256 = "27bf62d71cd64728ff43a9feb92f2ac2f2bf748986d856133cc1e51992428c25", - strip_prefix = "libdeflate-1.19", - urls = ["https://github.com/ebiggers/libdeflate/archive/refs/tags/v1.19.tar.gz"], - ) - - maybe( - http_archive, - name = "Imath", - build_file = "@com_openexr//:bazel/third_party/Imath.BUILD", - strip_prefix = "Imath-3.1.9", - sha256 = "f1d8aacd46afed958babfced3190d2d3c8209b66da451f556abd6da94c165cf3", - urls = ["https://github.com/AcademySoftwareFoundation/Imath/archive/refs/tags/v3.1.9.tar.gz"], - ) - - maybe( - http_archive, - name = "bazel_skylib", - sha256 = "66ffd9315665bfaafc96b52278f57c7e2dd09f5ede279ea6d39b2be471e7e3aa", - urls = [ - "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.4.2/bazel-skylib-1.4.2.tar.gz", - "https://github.com/bazelbuild/bazel-skylib/releases/download/1.4.2/bazel-skylib-1.4.2.tar.gz", - ], - ) diff --git a/cmake/CMakeLists.txt b/cmake/CMakeLists.txt index 3a4dbf991e..9348702611 100644 --- a/cmake/CMakeLists.txt +++ b/cmake/CMakeLists.txt @@ -74,15 +74,6 @@ configure_file(OpenEXRConfigInternal.h.in ${CMAKE_CURRENT_BINARY_DIR}/OpenEXRCon # make a temp copy in the binary dir for OpenEXRConfig.h to include configure_file(../src/lib/OpenEXRCore/openexr_version.h ${CMAKE_CURRENT_BINARY_DIR}/OpenEXRCore/openexr_version.h COPYONLY) -if(OPENEXR_INSTALL) -install( - FILES - ${CMAKE_CURRENT_BINARY_DIR}/OpenEXRConfig.h - DESTINATION - ${CMAKE_INSTALL_INCLUDEDIR}/${OPENEXR_OUTPUT_SUBDIR} -) -endif() - ################################################### ####### IexConfig.h and IexConfigInternal.h diff --git a/cmake/LibraryDefine.cmake b/cmake/LibraryDefine.cmake index 8df620ca30..14653430f3 100644 --- a/cmake/LibraryDefine.cmake +++ b/cmake/LibraryDefine.cmake @@ -104,7 +104,8 @@ function(OPENEXR_DEFINE_LIBRARY libname) string(TOUPPER "${CMAKE_BUILD_TYPE}" uppercase_CMAKE_BUILD_TYPE) set(verlibname ${CMAKE_SHARED_LIBRARY_PREFIX}${libname}${OPENEXR_LIB_SUFFIX}${CMAKE_${uppercase_CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX}) set(baselibname ${CMAKE_SHARED_LIBRARY_PREFIX}${libname}${CMAKE_${uppercase_CMAKE_BUILD_TYPE}_POSTFIX}${CMAKE_SHARED_LIBRARY_SUFFIX}) - install(CODE "execute_process(COMMAND ${CMAKE_COMMAND} -E chdir \"\$ENV\{DESTDIR\}${CMAKE_INSTALL_FULL_LIBDIR}\" ${CMAKE_COMMAND} -E create_symlink ${verlibname} ${baselibname})") + file(CREATE_LINK ${verlibname} ${CMAKE_CURRENT_BINARY_DIR}/${baselibname} SYMBOLIC) + install(FILES ${CMAKE_CURRENT_BINARY_DIR}/${baselibname} DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR}) install(CODE "message(STATUS \"Creating symlink ${CMAKE_INSTALL_FULL_LIBDIR}/${baselibname} -> ${verlibname}\")") set(verlibname) set(baselibname) diff --git a/cmake/OpenEXR.pc.in b/cmake/OpenEXR.pc.in index bce35c2b1f..68d71c3c84 100644 --- a/cmake/OpenEXR.pc.in +++ b/cmake/OpenEXR.pc.in @@ -14,7 +14,8 @@ Name: OpenEXR Description: OpenEXR image library Version: @OPENEXR_VERSION@ -Libs: @exr_pthread_libs@ -L${libdir} -lOpenEXR${libsuffix} -lOpenEXRUtil${libsuffix} -lOpenEXRCore${libsuffix} -lIex${libsuffix} -lIlmThread${libsuffix} @EXR_DEFLATE_LDFLAGS@ +Libs: @exr_pthread_libs@ -L${libdir} -lOpenEXR${libsuffix} -lOpenEXRUtil${libsuffix} -lOpenEXRCore${libsuffix} -lIex${libsuffix} -lIlmThread${libsuffix} Cflags: -I${includedir} -I${OpenEXR_includedir} @exr_pthread_cflags@ Requires: Imath +Requires.private: @EXR_DEFLATE_PKGCONFIG_REQUIRES@ diff --git a/cmake/OpenEXRSetup.cmake b/cmake/OpenEXRSetup.cmake index c0c15174b0..46af170e30 100644 --- a/cmake/OpenEXRSetup.cmake +++ b/cmake/OpenEXRSetup.cmake @@ -51,8 +51,22 @@ option(OPENEXR_ENABLE_LARGE_STACK "Enables code to take advantage of large stack ######################## ## Build related options -# Whether to build & install the various command line utility programs +option(OPENEXR_INSTALL "Install OpenEXR libraries/binaries/bindings" ON) + +# Whether to build & install the main libraries +option(OPENEXR_BUILD_LIBS "Enables building of main libraries" ON) + +# Whether to build the various command line utility programs option(OPENEXR_BUILD_TOOLS "Enables building of utility programs" ON) +option(OPENEXR_INSTALL_TOOLS "Install OpenEXR tools" ON) + +option(OPENEXR_BUILD_EXAMPLES "Build and install OpenEXR examples" ON) + +option(OPENEXR_BUILD_PYTHON "Build python bindings" OFF) + +option(OPENEXR_TEST_LIBRARIES "Run library tests" ON) +option(OPENEXR_TEST_TOOLS "Run tool tests" ON) +option(OPENEXR_TEST_PYTHON "Run python binding tests" ON) # This is a variable here for use in controlling where include files are # installed. Care must be taken when changing this, as many things @@ -138,6 +152,10 @@ if(OPENEXR_USE_CLANG_TIDY) ) endif() +if (NOT OPENEXR_BUILD_LIBS) + return() +endif() + ############################### # Dependent libraries @@ -160,15 +178,40 @@ set(OPENEXR_DEFLATE_TAG "v1.18" CACHE STRING "Tag to use for libdeflate source r if(NOT OPENEXR_FORCE_INTERNAL_DEFLATE) #TODO: ^^ Release should not clone from main, this is a place holder set(CMAKE_IGNORE_PATH "${CMAKE_CURRENT_BINARY_DIR}/_deps/deflate-src/config;${CMAKE_CURRENT_BINARY_DIR}/_deps/deflate-build/config") - include(FindPkgConfig) - pkg_check_modules(deflate IMPORTED_TARGET GLOBAL libdeflate) - set(CMAKE_IGNORE_PATH) - if (deflate_FOUND) - message(STATUS "Using libdeflate from ${deflate_LINK_LIBRARIES}") + # First try cmake config + find_package(libdeflate CONFIG QUIET) + if(libdeflate_FOUND) + if(TARGET libdeflate::libdeflate_shared) + set(EXR_DEFLATE_LIB libdeflate::libdeflate_shared) + else() + set(EXR_DEFLATE_LIB libdeflate::libdeflate_static) + endif() + set(EXR_DEFLATE_VERSION ${libdeflate_VERSION}) + message(STATUS "Using libdeflate from ${libdeflate_DIR}") + else() + # If not found, try pkgconfig + find_package(PkgConfig) + if(PKG_CONFIG_FOUND) + include(FindPkgConfig) + pkg_check_modules(deflate IMPORTED_TARGET GLOBAL libdeflate) + if(deflate_FOUND) + set(EXR_DEFLATE_LIB PkgConfig::deflate) + set(EXR_DEFLATE_VERSION ${deflate_VERSION}) + message(STATUS "Using libdeflate from ${deflate_LINK_LIBRARIES}") + endif() + endif() endif() + set(CMAKE_IGNORE_PATH) endif() -if(NOT TARGET PkgConfig::deflate AND NOT deflate_FOUND) +if(EXR_DEFLATE_LIB) + # Using external library + set(EXR_DEFLATE_SOURCES) + set(EXR_DEFLATE_INCLUDE_DIR) + # For OpenEXR.pc.in for static build + set(EXR_DEFLATE_PKGCONFIG_REQUIRES "libdeflate >= ${EXR_DEFLATE_VERSION}") +else() + # Using internal deflate if(OPENEXR_FORCE_INTERNAL_DEFLATE) message(STATUS "libdeflate forced internal, installing from ${OPENEXR_DEFLATE_REPO} (${OPENEXR_DEFLATE_TAG})") else() @@ -213,16 +256,6 @@ if(NOT TARGET PkgConfig::deflate AND NOT deflate_FOUND) list(TRANSFORM EXR_DEFLATE_SOURCES PREPEND ${deflate_SOURCE_DIR}/) set(EXR_DEFLATE_INCLUDE_DIR ${deflate_SOURCE_DIR}) set(EXR_DEFLATE_LIB) -else() - set(EXR_DEFLATE_INCLUDE_DIR) - set(EXR_DEFLATE_LIB ${deflate_LIBRARIES}) - # set EXR_DEFATE_LDFLAGS for OpenEXR.pc.in for static build - if (BUILD_SHARED_LIBS) - set(EXR_DEFLATE_LDFLAGS "") - else() - set(EXR_DEFLATE_LDFLAGS "-l${deflate_LIBRARIES}") - endif() - set(EXR_DEFLATE_SOURCES) endif() ####################################### @@ -231,10 +264,8 @@ endif() option(OPENEXR_FORCE_INTERNAL_IMATH "Force using an internal imath" OFF) # Check to see if Imath is installed outside of the current build directory. -set(OPENEXR_IMATH_REPO "https://github.com/AcademySoftwareFoundation/Imath.git" CACHE STRING - "Repo for auto-build of Imath") -set(OPENEXR_IMATH_TAG "main" CACHE STRING - "Tag for auto-build of Imath (branch, tag, or SHA)") +set(OPENEXR_IMATH_REPO "https://github.com/AcademySoftwareFoundation/Imath.git" CACHE STRING "Repo for auto-build of Imath") +set(OPENEXR_IMATH_TAG "main" CACHE STRING "Tag for auto-build of Imath (branch, tag, or SHA)") if(NOT OPENEXR_FORCE_INTERNAL_IMATH) #TODO: ^^ Release should not clone from main, this is a place holder set(CMAKE_IGNORE_PATH "${CMAKE_CURRENT_BINARY_DIR}/_deps/imath-src/config;${CMAKE_CURRENT_BINARY_DIR}/_deps/imath-build/config") @@ -259,10 +290,13 @@ if(NOT TARGET Imath::Imath AND NOT Imath_FOUND) if(NOT Imath_POPULATED) FetchContent_Populate(Imath) + # Propagate OpenEXR's install setting to Imath + set(IMATH_INSTALL ${OPENEXR_INSTALL}) + # Propagate OpenEXR's setting for pkg-config generation to Imath: # If OpenEXR is generating it, the internal Imath should, too. set(IMATH_INSTALL_PKG_CONFIG ${OPENEXR_INSTALL_PKG_CONFIG}) - + # hrm, cmake makes Imath lowercase for the properties (to imath) add_subdirectory(${imath_SOURCE_DIR} ${imath_BINARY_DIR}) endif() diff --git a/cmake/cmake_uninstall.cmake.in b/cmake/cmake_uninstall.cmake.in new file mode 100644 index 0000000000..1e5d2bb876 --- /dev/null +++ b/cmake/cmake_uninstall.cmake.in @@ -0,0 +1,23 @@ +# Source: https://gitlab.kitware.com/cmake/community/-/wikis/FAQ#can-i-do-make-uninstall-with-cmake + +if(NOT EXISTS "@CMAKE_BINARY_DIR@/install_manifest.txt") + message(FATAL_ERROR "Cannot find install manifest: @CMAKE_BINARY_DIR@/install_manifest.txt") +endif() + +file(READ "@CMAKE_BINARY_DIR@/install_manifest.txt" files) +string(REGEX REPLACE "\n" ";" files "${files}") +foreach(file ${files}) + message(STATUS "Uninstalling $ENV{DESTDIR}${file}") + if(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") + exec_program( + "@CMAKE_COMMAND@" ARGS + "-E remove \"$ENV{DESTDIR}${file}\"" + OUTPUT_VARIABLE rm_out + RETURN_VALUE rm_retval) + if(NOT "${rm_retval}" STREQUAL 0) + message(FATAL_ERROR "Problem when removing $ENV{DESTDIR}${file}") + endif() + else(IS_SYMLINK "$ENV{DESTDIR}${file}" OR EXISTS "$ENV{DESTDIR}${file}") + message(STATUS "File $ENV{DESTDIR}${file} does not exist.") + endif() +endforeach() diff --git a/docs/CMakeLists.txt b/docs/CMakeLists.txt index 928e008dcd..bf070f4e95 100644 --- a/docs/CMakeLists.txt +++ b/docs/CMakeLists.txt @@ -41,6 +41,7 @@ define_manpage(exrheader "print exr image header metadata") define_manpage(exrinfo "print exr image header metadata") define_manpage(exrmakepreview "generate exr preview thumbnail images") define_manpage(exrmaketiled "convert exr images to tiled format") +define_manpage(exrmanifest "print id manifest") define_manpage(exrmultipart "combine or split multipart exr images") define_manpage(exrmultiview "convert between single/multi-view exr images") define_manpage(exrstdattr "set exr image metadata") diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000000..7303862b6f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,66 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) Contributors to the OpenEXR Project. + +[build-system] +requires = ["scikit-build-core==0.8.1"] +build-backend = "scikit_build_core.build" + +[project] +name = "OpenEXR" +dynamic = ["version"] + +description="Python bindings for the OpenEXR image file format" +readme = "src/wrappers/python/README.md" +authors = [ + { name="Contributors to the OpenEXR project", email="info@openexr.com" }, +] +requires-python = ">=3.7" + +[project.urls] +"Homepage" = "https://openexr.com" +"Source" = "https://github.com/AcademySoftwareFoundation/OpenEXR" +"Bug Tracker" = "https://github.com/AcademySoftwareFoundation/OpenEXR/issues" + +[project.optional-dependencies] +test = ["pytest"] + +[tool.scikit-build] +wheel.expand-macos-universal-tags = true +sdist.exclude = [".github", "src/test", "src/examples", "website", "ASWF", "bazel", "share"] + +# Only build the PyOpenEXR (cmake --build --target PyOpenEXR). +cmake.targets = ["PyOpenEXR"] +# Only install the "python" component (cmake --install --component python). +# This makes sure that only files marked as "python" component are installed. +install.components = ["python"] + +# Enable experimental features if any are available +# In this case we need custom local plugin to get +# the project version from cmake. +experimental = true +metadata.version.provider = "openexr_skbuild_plugin" +metadata.version.provider-path = "./src/wrappers/python" + + +[tool.scikit-build.cmake.define] +OPENEXR_INSTALL = 'OFF' +OPENEXR_BUILD_PYTHON = 'ON' +OPENEXR_BUILD_EXAMPLES = 'OFF' +OPENEXR_BUILD_TOOLS = 'OFF' +OPENEXR_INSTALL_TOOLS = 'OFF' +OPENEXR_INSTALL_PKG_CONFIG = 'OFF' +OPENEXR_FORCE_INTERNAL_DEFLATE = 'ON' +OPENEXR_FORCE_INTERNAL_IMATH = 'ON' +OPENEXR_TEST_LIBRARIES = 'OFF' +BUILD_SHARED_LIBS = 'OFF' +CMAKE_POSITION_INDEPENDENT_CODE = 'ON' + +[tool.cibuildwheel] +test-command = "ctest -R PyOpenEXR" +test-extras = ["test"] +test-skip = ["*universal2:arm64"] +build-verbosity = 1 + +manylinux-x86_64-image = "manylinux2014" +manylinux-i686-image = "manylinux2014" +manylinux-aarch64-image = "manylinux2014" diff --git a/src/bin/CMakeLists.txt b/src/bin/CMakeLists.txt index 9a76d58071..99785681b1 100644 --- a/src/bin/CMakeLists.txt +++ b/src/bin/CMakeLists.txt @@ -1,18 +1,20 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) Contributors to the OpenEXR Project. -########################## -# Binaries / Utilities -########################## -if(OPENEXR_BUILD_TOOLS) - add_subdirectory( exr2aces ) - add_subdirectory( exrheader ) - add_subdirectory( exrinfo ) - add_subdirectory( exrmaketiled ) - add_subdirectory( exrstdattr ) - add_subdirectory( exrmakepreview ) - add_subdirectory( exrenvmap ) - add_subdirectory( exrmultiview ) - add_subdirectory( exrmultipart ) - add_subdirectory( exrcheck ) -endif() +################## +# Binaries / Tools +################## + +message(STATUS "Building OpenEXR tools") + +add_subdirectory( exr2aces ) +add_subdirectory( exrheader ) +add_subdirectory( exrinfo ) +add_subdirectory( exrmaketiled ) +add_subdirectory( exrstdattr ) +add_subdirectory( exrmakepreview ) +add_subdirectory( exrenvmap ) +add_subdirectory( exrmultiview ) +add_subdirectory( exrmultipart ) +add_subdirectory( exrcheck ) +add_subdirectory( exrmanifest ) diff --git a/src/bin/exrenvmap/main.cpp b/src/bin/exrenvmap/main.cpp index a5e97c3707..dda54dc4a2 100644 --- a/src/bin/exrenvmap/main.cpp +++ b/src/bin/exrenvmap/main.cpp @@ -40,6 +40,9 @@ usageMessage (ostream& stream, const char* program_name, bool verbose = false) if (verbose) { + std::string compressionNames; + getCompressionNamesString("/", compressionNames); + stream << "\n" "Convert an OpenEXR latitude-longitude environment map\n" "into a cube-face environment map or vice versa.\n" @@ -119,7 +122,7 @@ usageMessage (ostream& stream, const char* program_name, bool verbose = false) " -u sets level size rounding to ROUND_UP\n" "\n" " -z x sets the data compression method to x\n" - " (none/rle/zip/piz/pxr24/b44/b44a/dwaa/dwab,\n" + " (" << compressionNames.c_str() << ",\n" " default is zip)\n" "\n" " -v verbose mode\n" @@ -137,44 +140,8 @@ Compression getCompression (const string& str) { Compression c; - - if (str == "no" || str == "none" || str == "NO" || str == "NONE") - { - c = NO_COMPRESSION; - } - else if (str == "rle" || str == "RLE") - { - c = RLE_COMPRESSION; - } - else if (str == "zip" || str == "ZIP") - { - c = ZIP_COMPRESSION; - } - else if (str == "piz" || str == "PIZ") - { - c = PIZ_COMPRESSION; - } - else if (str == "pxr24" || str == "PXR24") - { - c = PXR24_COMPRESSION; - } - else if (str == "b44" || str == "B44") - { - c = B44_COMPRESSION; - } - else if (str == "b44a" || str == "B44A") - { - c = B44A_COMPRESSION; - } - else if (str == "dwaa" || str == "DWAA") - { - c = DWAA_COMPRESSION; - } - else if (str == "dwab" || str == "DWAB") - { - c = DWAB_COMPRESSION; - } - else + getCompressionIdFromName (str, c); + if (c == Compression::NUM_COMPRESSION_METHODS) { std::stringstream e; e << "Unknown compression method \"" << str << "\""; diff --git a/src/bin/exrheader/main.cpp b/src/bin/exrheader/main.cpp index b1e9ecbb63..46df4020df 100644 --- a/src/bin/exrheader/main.cpp +++ b/src/bin/exrheader/main.cpp @@ -43,32 +43,9 @@ using namespace std; void printCompression (Compression c) { - switch (c) - { - case NO_COMPRESSION: cout << "none"; break; - - case RLE_COMPRESSION: cout << "run-length encoding"; break; - - case ZIPS_COMPRESSION: cout << "zip, individual scanlines"; break; - - case ZIP_COMPRESSION: cout << "zip, multi-scanline blocks"; break; - - case PIZ_COMPRESSION: cout << "piz"; break; - - case PXR24_COMPRESSION: cout << "pxr24"; break; - - case B44_COMPRESSION: cout << "b44"; break; - - case B44A_COMPRESSION: cout << "b44a"; break; - - case DWAA_COMPRESSION: cout << "dwa, small scanline blocks"; break; - - case DWAB_COMPRESSION: cout << "dwa, medium scanline blocks"; break; - - case ZSTD_COMPRESSION: cout << "zstd"; break; - - default: cout << int (c); break; - } + std::string desc; + getCompressionDescriptionFromId(c, desc); + cout << desc.c_str(); } void diff --git a/src/bin/exrmaketiled/main.cpp b/src/bin/exrmaketiled/main.cpp index 884c570ef2..ac6020a0ce 100644 --- a/src/bin/exrmaketiled/main.cpp +++ b/src/bin/exrmaketiled/main.cpp @@ -37,6 +37,9 @@ usageMessage (ostream& stream, const char* program_name, bool verbose = false) if (verbose) { + std::string compressionNames; + getCompressionNamesString("/", compressionNames); + stream << "\n" "Read an OpenEXR image from infile, produce a tiled\n" "version of the image, and save the result in outfile.\n" @@ -71,7 +74,7 @@ usageMessage (ostream& stream, const char* program_name, bool verbose = false) " -u sets level size rounding to ROUND_UP\n" "\n" " -z x sets the data compression method to x\n" - " (none/rle/zip/piz/pxr24/b44/b44a/dwaa/dwab,\n" + " (" << compressionNames.c_str() << ",\n" " default is zip)\n" "\n" " -v verbose mode\n" @@ -93,44 +96,8 @@ Compression getCompression (const string& str) { Compression c; - - if (str == "no" || str == "none" || str == "NO" || str == "NONE") - { - c = NO_COMPRESSION; - } - else if (str == "rle" || str == "RLE") - { - c = RLE_COMPRESSION; - } - else if (str == "zip" || str == "ZIP") - { - c = ZIP_COMPRESSION; - } - else if (str == "piz" || str == "PIZ") - { - c = PIZ_COMPRESSION; - } - else if (str == "pxr24" || str == "PXR24") - { - c = PXR24_COMPRESSION; - } - else if (str == "b44" || str == "B44") - { - c = B44_COMPRESSION; - } - else if (str == "b44a" || str == "B44A") - { - c = B44A_COMPRESSION; - } - else if (str == "dwaa" || str == "DWAA") - { - c = DWAA_COMPRESSION; - } - else if (str == "dwab" || str == "DWAB") - { - c = DWAB_COMPRESSION; - } - else + getCompressionIdFromName (str, c); + if (c == Compression::NUM_COMPRESSION_METHODS) { std::stringstream e; e << "Unknown compression method \"" << str << "\""; diff --git a/src/bin/exrmanifest/CMakeLists.txt b/src/bin/exrmanifest/CMakeLists.txt new file mode 100644 index 0000000000..b5b04fb753 --- /dev/null +++ b/src/bin/exrmanifest/CMakeLists.txt @@ -0,0 +1,14 @@ +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) Contributors to the OpenEXR Project. + +add_executable(exrmanifest main.cpp) +target_link_libraries(exrmanifest OpenEXR::OpenEXR) +set_target_properties(exrmanifest PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" +) +if(OPENEXR_INSTALL_TOOLS) + install(TARGETS exrmanifest DESTINATION ${CMAKE_INSTALL_BINDIR}) +endif() +if(WIN32 AND BUILD_SHARED_LIBS) + target_compile_definitions(exrmanifest PRIVATE OPENEXR_DLL) +endif() diff --git a/src/bin/exrmanifest/main.cpp b/src/bin/exrmanifest/main.cpp new file mode 100644 index 0000000000..a0661c49db --- /dev/null +++ b/src/bin/exrmanifest/main.cpp @@ -0,0 +1,210 @@ + +// +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) Contributors to the OpenEXR Project. +// + +// +// output all the idmanifest information found in the file as plain text +// + +#include +#include +#include +#include + +#include +#include +#include + +using namespace OPENEXR_IMF_NAMESPACE; + +using std::cerr; +using std::cout; +using std::endl; +using std::exception; +using std::max; +using std::ostream; +using std::set; +using std::string; +using std::to_string; +using std::vector; + +size_t +dumpManifest (const IDManifest& mfst) +{ + + size_t uncompressedSize = 0; + + for (size_t i = 0; i < mfst.size (); ++i) + { + const IDManifest::ChannelGroupManifest& m = mfst[i]; + bool first = true; + if (i > 0) { cout << "\n\n"; } + cout << " channels : "; + for (set::const_iterator s = m.getChannels ().begin (); + s != m.getChannels ().end (); + ++s) + { + if (!first) { cout << ','; } + else { first = false; } + + cout << *s; + uncompressedSize += s->size () + 1; + } + + cout << "\n hashScheme: " << m.getHashScheme () << endl; + cout << " encoding : " << m.getEncodingScheme () << endl; + switch (m.getLifetime ()) + { + case IDManifest::LIFETIME_FRAME: + cout << " lifetime : frame\n"; + break; + case IDManifest::LIFETIME_SHOT: + cout << " lifetime : shot\n"; + break; + case IDManifest::LIFETIME_STABLE: + cout << " lifetime : stable\n"; + break; + } + + // + // compute max field sizes + // + size_t maxNumLen = 0; + vector componentLength (m.getComponents ().size ()); + for (size_t c = 0; c < m.getComponents ().size (); ++c) + { + size_t componentSize = m.getComponents ()[c].size (); + uncompressedSize += componentSize + 1; + componentLength[c] = max (componentLength[c], componentSize); + } + for (IDManifest::ChannelGroupManifest::ConstIterator q = m.begin (); + q != m.end (); + ++q) + { + + size_t stringLen = to_string (q.id ()).size (); + uncompressedSize += stringLen; + maxNumLen = max (maxNumLen, stringLen); + + for (size_t i = 0; i < q.text ().size (); i++) + { + uncompressedSize += q.text ()[i].size () + 1; + componentLength[i] = + max (componentLength[i], q.text ()[i].size ()); + } + } + + cout << " " << string (maxNumLen + 1, ' '); + for (size_t c = 0; c < m.getComponents ().size (); ++c) + { + string s = m.getComponents ()[c]; + cout << s << string (componentLength[c] + 1 - s.size (), ' '); + } + cout << endl; + for (IDManifest::ChannelGroupManifest::ConstIterator q = m.begin (); + q != m.end (); + ++q) + { + string id = to_string (q.id ()); + cout << " " << id << string (maxNumLen + 1 - id.size (), ' '); + for (size_t i = 0; i < q.text ().size (); i++) + { + string s = q.text ()[i]; + cout << s << string (componentLength[i] + 1 - s.size (), ' '); + } + cout << '\n'; + } + } + + return uncompressedSize; +} + +void +printManifest (const char fileName[]) +{ + + MultiPartInputFile in (fileName); + // + // extract objectID attribute + // + + for (int part = 0; part < in.parts (); part++) + { + if (in.parts () > 1) { cout << fileName << " part " << part << ":\n"; } + if (hasIDManifest (in.header (part))) + { + const Imf::CompressedIDManifest& mfst = + idManifest (in.header (part)); + size_t size = dumpManifest (mfst); + cout << "raw text size : " << size << endl; + cout << "uncompressed size: " << mfst._uncompressedDataSize << endl; + cout << "compressed size : " << mfst._compressedDataSize << endl; + } + else { cout << "no manifest found\n"; } + } +} + +void +usageMessage (ostream& stream, const char* program_name, bool verbose = false) +{ + stream << "Usage: " << program_name << " imagefile [imagefile ...]\n"; + + if (verbose) + stream + << "\n" + "Read exr files and print the contents of the embedded manifest.\n" + "\n" + "Options:\n" + " -h, --help print this message\n" + " --version print version information\n" + "\n" + "Report bugs via https://github.com/AcademySoftwareFoundation/openexr/issues or email security@openexr.com\n" + ""; +} + +int +main (int argc, char* argv[]) +{ + + if (argc < 2) + { + usageMessage (cerr, argv[0], false); + return -1; + } + + for (int i = 1; i < argc; ++i) + { + if (!strcmp (argv[i], "-h") || !strcmp (argv[1], "--help")) + { + usageMessage (cout, "exrmanifest", true); + return 0; + } + else if (!strcmp (argv[i], "--version")) + { + const char* libraryVersion = getLibraryVersion (); + + cout << "exrmanifest (OpenEXR) " << OPENEXR_VERSION_STRING; + if (strcmp (libraryVersion, OPENEXR_VERSION_STRING)) + cout << "(OpenEXR version " << libraryVersion << ")"; + cout << " https://openexr.com" << endl; + cout << "Copyright (c) Contributors to the OpenEXR Project" << endl; + cout << "License BSD-3-Clause" << endl; + return 0; + } + } + + try + { + for (int i = 1; i < argc; ++i) + printManifest (argv[i]); + } + catch (const exception& e) + { + cerr << argv[0] << ": " << e.what () << endl; + return 1; + } + + for (int i = 1; i < argc; ++i) {} +} diff --git a/src/bin/exrmultiview/main.cpp b/src/bin/exrmultiview/main.cpp index 64bdb37cf4..c537a1d5c2 100644 --- a/src/bin/exrmultiview/main.cpp +++ b/src/bin/exrmultiview/main.cpp @@ -35,6 +35,10 @@ usageMessage (ostream& stream, const char* program_name, bool verbose = false) stream << "Usage: " << program_name << " [options] viewname1 infile1 viewname2 infile2 ... outfile" << endl; if (verbose) + { + std::string compressionNames; + getCompressionNamesString("/", compressionNames); + stream << "\n" "Combine two or more single-view OpenEXR image files into\n" "a single multi-view image file. On the command line,\n" @@ -51,7 +55,7 @@ usageMessage (ostream& stream, const char* program_name, bool verbose = false) "Options:\n" "\n" " -z x sets the data compression method to x\n" - " (none/rle/zip/piz/pxr24/b44/b44a/dwaa/dwab,\n" + " (" << compressionNames.c_str() << ",\n" " default is piz)\n" "\n" " -v verbose mode\n" @@ -62,50 +66,15 @@ usageMessage (ostream& stream, const char* program_name, bool verbose = false) "\n" "Report bugs via https://github.com/AcademySoftwareFoundation/openexr/issues or email security@openexr.com\n" ""; + } } Compression getCompression (const string& str) { Compression c; - - if (str == "no" || str == "none" || str == "NO" || str == "NONE") - { - c = NO_COMPRESSION; - } - else if (str == "rle" || str == "RLE") - { - c = RLE_COMPRESSION; - } - else if (str == "zip" || str == "ZIP") - { - c = ZIP_COMPRESSION; - } - else if (str == "piz" || str == "PIZ") - { - c = PIZ_COMPRESSION; - } - else if (str == "pxr24" || str == "PXR24") - { - c = PXR24_COMPRESSION; - } - else if (str == "b44" || str == "B44") - { - c = B44_COMPRESSION; - } - else if (str == "b44a" || str == "B44A") - { - c = B44A_COMPRESSION; - } - else if (str == "dwaa" || str == "DWAA") - { - c = DWAA_COMPRESSION; - } - else if (str == "dwab" || str == "DWAB") - { - c = DWAB_COMPRESSION; - } - else + getCompressionIdFromName (str, c); + if (c == Compression::NUM_COMPRESSION_METHODS) { std::stringstream e; e << "Unknown compression method \"" << str << "\""; diff --git a/src/bin/exrstdattr/main.cpp b/src/bin/exrstdattr/main.cpp index 4101993916..636c9a940a 100644 --- a/src/bin/exrstdattr/main.cpp +++ b/src/bin/exrstdattr/main.cpp @@ -848,6 +848,9 @@ main (int argc, char** argv) outPart.copyPixels (inPart); } } + + for (size_t i=0; i +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include + +using namespace OPENEXR_IMF_NAMESPACE; + +using std::cerr; +using std::set; +using std::sort; +using std::vector; + +void +printHelp () +{ + cerr + << "syntax: deepidexample options output.deep.exr\n\n"; + cerr << "--multivariate : combine 'material' and 'model' name into a single ID channel, rather than separate channels\n"; + cerr << "--64 : use 64 bit hashes in two channels, rather than a single channel 32 bit hash\n"; + cerr << "--frame number : specify animation frame number. Animation cycles every 200 frames\n"; + cerr << "--objectid : store object ids in a simple stringvector format rather than the idmanifest attribute\n"; + cerr << "--size width height : specify image dimensions for output (default 256 256)\n"; + cerr << "--count number : number of objects to write (default 100)\n"; +} + +// +// a 'sample' as written to the deep file, with RGBA, depth, and up to five ID channels +// +struct Rgbaz +{ + half r, g, b, a, z; + uint32_t id0, id1, id2, id3, id4; + + // + // depth sort (use ID to resolve ties) + // + bool operator<(const Rgbaz& other) const + { + if (z < other.z) { return true; } + if (z > other.z) { return false; } + return id4 < other.id4; + } +}; + +// +// object model names +// +static const char* shapeNames[] = {"blob", "circle"}; +static const char* sizeNames[] = {"small", "medium", "big"}; + +// +// seven colors, names and RGB values of each +// +static const char* colorNames[] = { + "white", "red", "green", "blue", "cyan", "magenta", "yellow"}; +struct Rgbaz colors[] = { + {0.9, 0.9, 0.9}, + {0.9, 0.1, 0.1}, + {0.1, 0.9, 0.1}, + {0.1, 0.1, 0.9}, + {0.1, 0.9, 0.9}, + {0.9, 0.1, 0.9}, + {0.9, 0.9, 0.1}}; + +int random (std::default_random_engine& generator, int max); + +void drawBlob ( + std::vector>& image, + int width, + int height, + float x, + float y, + float z, + int size, + int color, + const uint32_t* ids); +void drawCircle ( + std::vector>& image, + int width, + int height, + float x, + float y, + float z, + int size, + int color, + const uint32_t* ids); + +int +main (int argc, char* argv[]) +{ + char* outputFile = nullptr; + bool hash64 = false; + bool multivariate = false; + bool objectID = false; + int width = 256; + int height = 256; + int count = 100; + int frame = 0; + + // parse options + for (int i = 1; i < argc; ++i) + { + if (strncmp (argv[i], "--64", 3) == 0) { hash64 = true; } + else if (strncmp (argv[i], "--multivariate", 3) == 0) + { + multivariate = true; + } + else if (strncmp (argv[i], "--help", 3) == 0) + { + printHelp (); + return 0; + } + else if (strncmp (argv[i], "--size", 3) == 0) + { + if (argc < i + 2) + { + printHelp (); + return 1; + } + width = atoi (argv[i + 1]); + height = atoi (argv[i + 2]); + i += 2; + } + else if (strncmp (argv[i], "--count", 3) == 0) + { + if (argc < i + 1) + { + printHelp (); + return 1; + } + count = atoi (argv[i + 1]); + i += 1; + } + else if (strncmp (argv[i], "--frame", 3) == 0) + { + if (argc < i + 1) + { + printHelp (); + return 1; + } + frame = atoi (argv[i + 1]); + i += 1; + } + else if (strncmp (argv[i], "--objectid", 3) == 0) { objectID = true; } + else if (outputFile == nullptr) { outputFile = argv[i]; } + else + { + printHelp (); + return 1; + } + } + + if (outputFile == nullptr) + { + cerr << "error: need to specify output filename\n"; + printHelp (); + return 1; + } + + if (objectID) + { + if (!multivariate || hash64) + { + cerr + << "error: --objectid mode only works with --multivariate on and --64 off\n"; + return 1; + } + } + + // + // initialize manifest object + // + + IDManifest::ChannelGroupManifest modelOrMultiManifest; + IDManifest::ChannelGroupManifest materialManifest; + + std::vector oldObjectID; + + if (multivariate) + { + if (hash64) + { + set ids; + ids.insert ("id0"); + ids.insert ("id1"); + modelOrMultiManifest.setChannels (ids); + modelOrMultiManifest.setEncodingScheme (IDManifest::ID2_SCHEME); + modelOrMultiManifest.setHashScheme (IDManifest::MURMURHASH3_64); + } + else + { + modelOrMultiManifest.setChannel ("id"); + modelOrMultiManifest.setEncodingScheme (IDManifest::ID_SCHEME); + modelOrMultiManifest.setHashScheme (IDManifest::MURMURHASH3_32); + } + vector components (2); + components[0] = "model"; + components[1] = "material"; + modelOrMultiManifest.setComponents (components); + } + else + { + if (hash64) + { + set model; + model.insert ("model.id0"); + model.insert ("model.id1"); + modelOrMultiManifest.setChannels (model); + modelOrMultiManifest.setEncodingScheme (IDManifest::ID2_SCHEME); + modelOrMultiManifest.setHashScheme (IDManifest::MURMURHASH3_64); + + set material; + material.insert ("material.id0"); + material.insert ("material.id1"); + materialManifest.setChannels (material); + materialManifest.setEncodingScheme (IDManifest::ID2_SCHEME); + materialManifest.setHashScheme (IDManifest::MURMURHASH3_64); + } + else + { + modelOrMultiManifest.setChannel ("modelid"); + materialManifest.setChannel ("materialid"); + + modelOrMultiManifest.setEncodingScheme (IDManifest::ID_SCHEME); + modelOrMultiManifest.setHashScheme (IDManifest::MURMURHASH3_32); + materialManifest.setEncodingScheme (IDManifest::ID_SCHEME); + materialManifest.setHashScheme (IDManifest::MURMURHASH3_32); + } + modelOrMultiManifest.setComponent ("model"); + materialManifest.setComponent ("material"); + } + + modelOrMultiManifest.setLifetime (IDManifest::LIFETIME_STABLE); + materialManifest.setLifetime (IDManifest::LIFETIME_STABLE); + + // + // draw image + // + + vector> deepImage (width * height); + + std::default_random_engine generator; + generator.seed (2); + + uint32_t ids[5]; + + // + // animation oscillates between two random positions + // over 100 frames + // + float blend = 0.5 - cos (double (frame) * M_PI / 100.) / 2; + for (int object = 0; object < count; ++object) + { + int shape = random (generator, 1); + int size = random (generator, 2); + int color = random (generator, 6); + + // + // generate ID + // + if (multivariate) + { + vector s (2); + s[0] = string (shapeNames[shape]) + "/" + string (sizeNames[size]); + s[1] = colorNames[color]; + uint64_t hash = modelOrMultiManifest.insert (s); + ids[0] = hash & 0xFFFFFFFF; + + // only needed for 64 bit hash scheme: store most significant 32 bits in ids[1] + ids[1] = hash >> 32; + + if (objectID) + { + oldObjectID.push_back ( + s[0] + "," + s[1] + "," + std::to_string (ids[0])); + } + } + else + { + uint64_t hash = modelOrMultiManifest.insert ( + string (shapeNames[shape]) + "/" + string (sizeNames[size])); + ids[0] = hash & 0xFFFFFFFF; + ids[1] = hash >> 32; + hash = materialManifest.insert (colorNames[color]); + ids[2] = hash & 0xFFFFFFFF; + ids[3] = hash >> 32; + } + + ids[4] = object; // particle ID + + // + // randomized position, velocity, depth + // + int x1 = random (generator, width); + int y1 = random (generator, height); + int x2 = random (generator, width); + int y2 = random (generator, height); + float z = random (generator, 4096) / 2.f; + + float x = blend * x2 + (1.0 - blend) * x1; + float y = blend * y2 + (1.0 - blend) * y1; + + if (shape == 0) + { + drawBlob (deepImage, width, height, x, y, z, size, color, ids); + } + else + { + drawCircle (deepImage, width, height, x, y, z, size, color, ids); + } + } + + // + // initialize pointers required by OpenEXR API to indicate address of first sample of each channel of each pixel + // + + vector sampleCounts (width * height); + vector ptrR (width * height); + vector ptrG (width * height); + vector ptrB (width * height); + vector ptrA (width * height); + vector ptrZ (width * height); + vector ptrID0 (width * height); + vector ptrID1 (width * height); + vector ptrID2 (width * height); + vector ptrID3 (width * height); + vector ptrID4 (width * height); + for (int i = 0; i < width * height; ++i) + { + sampleCounts[i] = int (deepImage[i].size ()); + if (sampleCounts[i] > 0) + { + // + // store samples depth sorted + // + sort (deepImage[i].begin (), deepImage[i].end ()); + ptrR[i] = (char*) &deepImage[i][0].r; + ptrG[i] = (char*) &deepImage[i][0].g; + ptrB[i] = (char*) &deepImage[i][0].b; + ptrA[i] = (char*) &deepImage[i][0].a; + ptrZ[i] = (char*) &deepImage[i][0].z; + ptrID0[i] = (char*) &deepImage[i][0].id0; + ptrID1[i] = (char*) &deepImage[i][0].id1; + ptrID2[i] = (char*) &deepImage[i][0].id2; + ptrID3[i] = (char*) &deepImage[i][0].id3; + ptrID4[i] = (char*) &deepImage[i][0].id4; + } + } + + // save file + + Header h (width, height); + h.compression () = ZIPS_COMPRESSION; + + DeepFrameBuffer buf; + buf.insertSampleCountSlice (Slice ( + UINT, + (char*) sampleCounts.data (), + sizeof (int), + sizeof (int) * width)); + + h.channels ().insert ("R", Channel (HALF)); + buf.insert ( + "R", + DeepSlice ( + HALF, + (char*) ptrR.data (), + sizeof (char*), + sizeof (char*) * width, + sizeof (Rgbaz))); + + h.channels ().insert ("G", Channel (HALF)); + buf.insert ( + "G", + DeepSlice ( + HALF, + (char*) ptrG.data (), + sizeof (char*), + sizeof (char*) * width, + sizeof (Rgbaz))); + + h.channels ().insert ("B", Channel (HALF)); + buf.insert ( + "B", + DeepSlice ( + HALF, + (char*) ptrB.data (), + sizeof (char*), + sizeof (char*) * width, + sizeof (Rgbaz))); + + h.channels ().insert ("A", Channel (HALF)); + buf.insert ( + "A", + DeepSlice ( + HALF, + (char*) ptrA.data (), + sizeof (char*), + sizeof (char*) * width, + sizeof (Rgbaz))); + + h.channels ().insert ("Z", Channel (HALF)); + buf.insert ( + "Z", + DeepSlice ( + HALF, + (char*) ptrZ.data (), + sizeof (char*), + sizeof (char*) * width, + sizeof (Rgbaz))); + + if (multivariate) + { + if (hash64) + { + h.channels ().insert ("id.id0", UINT); + buf.insert ( + "id.id0", + DeepSlice ( + UINT, + (char*) ptrID0.data (), + sizeof (char*), + sizeof (char*) * width, + sizeof (Rgbaz))); + h.channels ().insert ("id.id1", UINT); + buf.insert ( + "id.id1", + DeepSlice ( + UINT, + (char*) ptrID1.data (), + sizeof (char*), + sizeof (char*) * width, + sizeof (Rgbaz))); + } + else + { + h.channels ().insert ("id", UINT); + buf.insert ( + "id", + DeepSlice ( + UINT, + (char*) ptrID0.data (), + sizeof (char*), + sizeof (char*) * width, + sizeof (Rgbaz))); + } + } + else + { + if (hash64) + { + h.channels ().insert ("model.id0", UINT); + buf.insert ( + "model.id0", + DeepSlice ( + UINT, + (char*) ptrID0.data (), + sizeof (char*), + sizeof (char*) * width, + sizeof (Rgbaz))); + h.channels ().insert ("model.id1", UINT); + buf.insert ( + "model.id1", + DeepSlice ( + UINT, + (char*) ptrID1.data (), + sizeof (char*), + sizeof (char*) * width, + sizeof (Rgbaz))); + + h.channels ().insert ("material.id0", UINT); + buf.insert ( + "material.id0", + DeepSlice ( + UINT, + (char*) ptrID2.data (), + sizeof (char*), + sizeof (char*) * width, + sizeof (Rgbaz))); + h.channels ().insert ("material.id1", UINT); + buf.insert ( + "material.id1", + DeepSlice ( + UINT, + (char*) ptrID3.data (), + sizeof (char*), + sizeof (char*) * width, + sizeof (Rgbaz))); + } + else + { + h.channels ().insert ("modelid", UINT); + buf.insert ( + "modelid", + DeepSlice ( + UINT, + (char*) ptrID0.data (), + sizeof (char*), + sizeof (char*) * width, + sizeof (Rgbaz))); + h.channels ().insert ("materialid", UINT); + buf.insert ( + "materialid", + DeepSlice ( + UINT, + (char*) ptrID2.data (), + sizeof (char*), + sizeof (char*) * width, + sizeof (Rgbaz))); + } + } + + h.channels ().insert ("particleid", UINT); + buf.insert ( + "particleid", + DeepSlice ( + UINT, + (char*) ptrID4.data (), + sizeof (char*), + sizeof (char*) * width, + sizeof (Rgbaz))); + + if (objectID) + { + h.insert ("objectID", StringVectorAttribute (oldObjectID)); + } + else + { + IDManifest manifest; + manifest.add (modelOrMultiManifest); + if (!multivariate) { manifest.add (materialManifest); } + IDManifest::ChannelGroupManifest particleManifest; + particleManifest.setChannel ("particleid"); + particleManifest.setEncodingScheme (IDManifest::ID_SCHEME); + particleManifest.setHashScheme (IDManifest::NOTHASHED); + particleManifest.setLifetime (IDManifest::LIFETIME_SHOT); + manifest.add (particleManifest); + + addIDManifest (h, manifest); + } + + // + // samples are depth sorted, and do not overlap + // + addDeepImageState (h, DIS_TIDY); + + DeepScanLineOutputFile file (outputFile, h); + file.setFrameBuffer (buf); + file.writePixels (height); +} + +int +random (std::default_random_engine& generator, int max) +{ + std::uniform_int_distribution dist (0, max); + return dist (generator); +} + +void +drawBlob ( + std::vector>& image, + int width, + int height, + float x, + float y, + float z, + int size, + int color, + const uint32_t* ids) +{ + // + // draw windowed gaussian centered at (x,y) + // + + float sigma = 5 + 40 * size; + + int ptr = 0; + + Rgbaz point; + point.z = z; + point.id0 = ids[0]; + point.id1 = ids[1]; + point.id2 = ids[2]; + point.id3 = ids[3]; + point.id4 = ids[4]; + + for (int j = 0; j < height; ++j) + { + for (int i = 0; i < width; ++i) + { + float dist_sq = (x - i) * (x - i) + (j - y) * (j - y); + float scale = + exp (-dist_sq / sigma) * (1.f - sqrt (dist_sq) / sigma); + + if (scale > 0.001) + { + point.r = colors[color].r * scale; + point.g = colors[color].g * scale; + point.b = colors[color].b * scale; + point.a = scale; + + image[ptr].push_back (point); + } + + ptr++; + } + } +} + +float +getAlpha (float xmin, float ymin, float xmax, float ymax, float size) +{ + bool in1 = xmin * xmin + ymin * ymin < size * size; + bool in2 = xmax * xmax + ymin * ymin < size * size; + bool in3 = xmin * xmin + ymax * ymax < size * size; + bool in4 = xmin * xmin + ymax * ymax < size * size; + + if (in1 && in2 && in3 && in4) { return (xmax - xmin) * (ymax - ymin); } + if (!in1 && !in2 && !in3 && !in4) { return 0.f; } + if (xmax - xmin < 0.001 && ymax - ymin < 0.001) { return 0.f; } + return getAlpha (xmin, ymin, (xmin + xmax) / 2, (ymin + ymax) / 2, size) + + getAlpha ((xmin + xmax) / 2, ymin, xmax, (ymin + ymax) / 2, size) + + getAlpha (xmin, (ymin + ymax) / 2, (xmin + xmax) / 2, ymax, size) + + getAlpha ((xmin + xmax) / 2, (ymin + ymax) / 2, xmax, ymax, size); +} + +void +drawCircle ( + std::vector>& image, + int width, + int height, + float x, + float y, + float z, + int size, + int color, + const uint32_t* ids) +{ + int ptr = 0; + float radius = 3 + 8 * size; + Rgbaz point; + point.z = z; + point.id0 = ids[0]; + point.id1 = ids[1]; + point.id2 = ids[2]; + point.id3 = ids[3]; + point.id4 = ids[4]; + + for (int j = 0; j < height; ++j) + { + for (int i = 0; i < width; ++i) + { + float alpha = getAlpha ( + i - x - 0.5f, j - y - 0.5f, i - x + 0.5f, j - y + 0.5f, radius); + if (alpha > 0) + { + point.r = colors[color].r * alpha; + point.g = colors[color].g * alpha; + point.b = colors[color].b * alpha; + point.a = alpha; + + image[ptr].push_back (point); + } + ptr++; + } + } +} diff --git a/src/examples/deepidselect.cpp b/src/examples/deepidselect.cpp new file mode 100644 index 0000000000..dab49cda65 --- /dev/null +++ b/src/examples/deepidselect.cpp @@ -0,0 +1,540 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) Contributors to the OpenEXR Project. +// + +// +// example of using an IDManifest to locate given objects in a deep image with IDs +// demonstrates how to use multivariate IDs, and 64 bit IDs spread across two channels +// deepidexample will create images that can be used as input +// (though this tool is intended to support images from other sources) +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace OPENEXR_IMF_NAMESPACE; +using namespace IMATH_NAMESPACE; + +using std::cerr; +using std::dec; +using std::endl; +using std::hex; +using std::list; +using std::map; +using std::set; +using std::stoi; +using std::vector; + +struct match +{ + int channel1; // index of first channel to look up ID in + uint32_t id1; // first ID + int channel2; // index of second channel, or -1 if only one channel + uint32_t id2; // second ID, ignored channel2==-1 +}; + +// +// setIds parses the matches arguments, and populates lists of matching IDs. If there are '--and' statements in the matches +// ids are entered in a new list +// +void setIds ( + const IDManifest& mfst, + list>& ids, + const char* matches[], + int numMatches, + const map& channelToPos); + +int +main (int argc, const char* argv[]) +{ + if (argc < 4) + { + cerr + << "syntax: [--mask] input.exr match [match...] [--and match [match...]] output.exr\n" + << " if --mask specified, writes a shallow EXR with a mask of the selected object(s) in the 'A' channel\n" + << " otherwise, writes a deep EXR only containing the selected object(s)\n" + << '\n' + << " matches can be:\n" + << " searchstring - match any component of any channel\n" + << " componentname:searchstring - only match given component\n" + << " channelname:number - match specified numeric ID in given channel\n" + << '\n' + << "\"A B --and C D\" means \"(must match either A or B) and also (must match either C or D)\"\n" + << " e.g:\n" + << " input.deep.exr blue output.deep.exr\n" + << " input.deep.exr material:blue --and model:blob output.deep.exr\n" + << " input.deep.exr material:blue material:red --and model:blob output.deep.exr\n" + << " input.deep.exr particleid:3 output.deep.exr\n"; + return 1; + } + + bool mask = false; + int numMatchArguments = argc - 2; + const char** matchArguments = argv + 2; + const char* inputFile = argv[1]; + if (strcmp (argv[1], "--mask") == 0) + { + mask = true; + numMatchArguments--; + matchArguments++; + inputFile = argv[2]; + } + + MultiPartInputFile input (inputFile); + + if (!hasIDManifest (input.header (0))) + { + cerr << "deepidselect requires an ID manifest in the EXR header\n"; + return 1; + } + + for (int i = 0; i < input.parts (); ++i) + { + if (input.header (i).type () != DEEPSCANLINE) + { + cerr + << "deepidselect currently only supports files which are entirely deep scanline files\n"; + return 1; + } + } + + // + // build output headers. For deep output, this is easy: just copy them over + // for masks, build scanline images of the same dimensions, each with a single alpha channel + // + vector
hdrs (input.parts ()); + if (mask) + { + for (int h = 0; h < input.parts (); ++h) + { + const Header& inHdr = input.header (h); + hdrs[h].dataWindow () = inHdr.dataWindow (); + hdrs[h].setType (SCANLINEIMAGE); + hdrs[h].displayWindow () = inHdr.displayWindow (); + if (inHdr.hasView ()) { hdrs[h].setView (inHdr.view ()); } + if (inHdr.hasName ()) { hdrs[h].setName (inHdr.name ()); } + hdrs[h].channels ().insert ("A", Channel (HALF)); + } + } + else + { + for (int h = 0; h < input.parts (); ++h) + { + hdrs[h] = input.header (h); + } + } + + MultiPartOutputFile output (argv[argc - 1], hdrs.data (), input.parts ()); + + // process each part individually + + for (int pt = 0; pt < input.parts (); ++pt) + { + const Header& inputHeader = input.header (pt); + const ChannelList& inputChans = inputHeader.channels (); + Box2i dataWindow = inputHeader.dataWindow (); + int width = dataWindow.max.x + 1 - dataWindow.min.x; + + map + channelToPos; // index of each channel as stored in scanLine object + + int alphaChannel = -1; + PixelType alphaChannelType = HALF; + + int channels = 0; + for (ChannelList::ConstIterator i = inputChans.begin (); + i != inputChans.end (); + ++i) + { + channelToPos[i.name ()] = channels; + if (strcmp (i.name (), "A") == 0) + { + alphaChannel = channels; + alphaChannelType = i.channel ().type; + } + channels++; + } + + // if the part has a manifest, use this part's manifest + // otherwise use part 0's manifest. + // (but reparse the manifest for every part in case the channel list has changed) + // + int manifestPart = hasIDManifest (inputHeader) ? pt : 0; + list> ids; + IDManifest mfst = idManifest (input.header (manifestPart)); + + setIds (mfst, ids, matchArguments, numMatchArguments, channelToPos); + + // store for an individual deep scanline. Accessed using scanLine[channelIndex][pixelIndex][sampleIndex] + // where pixelIndex is 0 for the leftmost pixel (even if the dataWindow doesn't start at 0) + vector>> scanLine (channels); + + // pointers to the data in each channel for FrameBuffer + vector> scanPointers (channels); + + for (int i = 0; i < channels; ++i) + { + scanLine[i].resize (width); + scanPointers[i].resize (width); + } + + vector pixelCounts (width); + vector outputAlpha ( + width); // only required for --mask mode: stores output + + DeepFrameBuffer buf; + buf.insertSampleCountSlice (Slice ( + UINT, + (char*) pixelCounts.data () - dataWindow.min.x, + sizeof (int), + 0)); + int c = 0; + + // + // read all channels as their native type, so they round trip when writing deep + // + for (ChannelList::ConstIterator i = inputChans.begin (); + i != inputChans.end (); + ++i, ++c) + { + buf.insert ( + i.name (), + DeepSlice ( + i.channel ().type, + (char*) scanPointers[c].data () - dataWindow.min.x, + sizeof (char*), + 0, + sizeof (int32_t))); + } + + DeepScanLineInputPart inPart (input, pt); + inPart.setFrameBuffer (buf); + + // + // for mask, create an alpha channel and initialize a FrameBuffer with that data + // otherwise, can use the deep frame buffer for both input and output, since the data is processed in-place + // + if (mask) + { + FrameBuffer outBuf; + outBuf.insert ( + "A", + Slice ( + HALF, + (char*) outputAlpha.data () - dataWindow.min.x, + sizeof (half), + 0)); + OutputPart outPart (output, pt); + outPart.setFrameBuffer (outBuf); + } + else + { + DeepScanLineOutputPart outPart (output, pt); + outPart.setFrameBuffer (buf); + } + + for (int y = dataWindow.min.y; y <= dataWindow.max.y; ++y) + { + inPart.readPixelSampleCounts (y); + for (int c = 0; c < channels; ++c) + { + for (int x = 0; x < width; ++x) + { + scanLine[c][x].resize (pixelCounts[x]); + scanPointers[c][x] = scanLine[c][x].data (); + } + } + + inPart.readPixels (y); + + for (int x = 0; x < width; ++x) + { + int outputSample = 0; + + float totalAlpha = 0.f; + float maskAlpha = 0.f; + + for (int s = 0; s < pixelCounts[x]; ++s) + { + // + // should sample s be retained? + // look for an entry in each 'and group' where all the required channels match their + // corresponding values + // + + bool good = true; + for (list>::const_iterator idGroup = + ids.begin (); + idGroup != ids.end () && good; + ++idGroup) + { + good = false; + for (list::const_iterator i = idGroup->begin (); + i != idGroup->end () && !good; + ++i) + { + if (scanLine[i->channel1][x][s] == i->id1 && + (i->channel2 == -1 || + scanLine[i->channel2][x][s] == i->id2)) + { + good = true; + } + } + } + + // + // deep output mode: + // delete unwanted samples + // mask output node: + // composite together wanted sample's alpha + // + + if (mask) + { + //cast alpha to float, and composite + float alpha = 0.f; + switch (alphaChannelType) + { + case FLOAT: + alpha = *(float*) (&scanLine[alphaChannel][x] + .data ()[s]); + break; + case HALF: + alpha = *(half*) (&scanLine[alphaChannel][x] + .data ()[s]); + break; + case UINT: + alpha = scanLine[alphaChannel][x][s]; + break; //wat! this is a weird thing to do,but whatever... + case NUM_PIXELTYPES: break; + } + + if (good) { maskAlpha += (1.0 - totalAlpha) * alpha; } + totalAlpha += (1.0 - totalAlpha) * alpha; + } + + else if (good) + { + // keep Sample: copy from original position into output position + // (so overwrite any samples that are to be deleted) + for (int c = 0; c < channels; ++c) + { + scanLine[c][x][outputSample] = scanLine[c][x][s]; + } + outputSample++; + } + } + + if (mask) + { + if (totalAlpha > 0.f) { maskAlpha /= totalAlpha; } + outputAlpha[x] = maskAlpha; + } + else + { + // update total count of samples + pixelCounts[x] = outputSample; + } + } + + // + // write data out + // + if (mask) + { + OutputPart outPart (output, pt); + outPart.writePixels (1); + } + else + { + DeepScanLineOutputPart outPart (output, pt); + outPart.writePixels (1); + } + } + } +} + +void +setIds ( + const IDManifest& mfst, + list>& ids, + const char* matches[], + int numMatches, + const map& channelToPos) +{ + + // + // initially one single list + // + ids.clear (); + ids.push_back (list ()); + + // + // check each manifest, each entry, against each matching expression + // + for (int c = 0; c < numMatches; ++c) + { + + // + // an 'and' argument means proceed to the next group of ids + // must find a match in every such group + // + if (strcmp (matches[c], "--and") == 0) + { + ids.push_back (list ()); + continue; + } + + string matchString (matches[c]); + + // + // handle strings of the form component:searchstring + // and channel:idnumber + // + string componentName; + string::size_type pos = matchString.find (':'); + if (pos != string::npos) + { + componentName = matchString.substr (0, pos); + matchString = matchString.substr (pos + 1); + } + + if (matchString.find_first_not_of ("0123456789") == string::npos) + { + map::const_iterator chan = + channelToPos.find (componentName); + if (chan != channelToPos.end ()) + { + match m; + m.channel1 = chan->second; + m.id1 = stoi (matchString); + m.channel2 = -1; + ids.back ().push_back (m); + } + continue; // skip parsing the manifests for this string + } + + // check the manifest for each group of channels + for (size_t i = 0; i < mfst.size (); ++i) + { + + for (IDManifest::ChannelGroupManifest::ConstIterator it = + mfst[i].begin (); + it != mfst[i].end (); + ++it) + { + for (size_t stringIndex = 0; stringIndex < it.text ().size (); + ++stringIndex) + { + if (componentName == "" || + mfst[i].getComponents ()[stringIndex] == componentName) + { + // simple substring matching only: could do wildcards or regexes here instead + if (it.text ()[stringIndex].find (matchString) != + string::npos) + { + // a match is found - add it to the corresponding channels + if (mfst[i].getEncodingScheme () == + IDManifest::ID_SCHEME) + { + // simple scheme: the ID channel has to match + for (const string& s: mfst[i].getChannels ()) + { + map::const_iterator chan = + channelToPos.find (s); + if (chan != channelToPos.end ()) + { + //could support matching the ID against a specific channel + //that check would happen here + + match m; + m.channel1 = chan->second; + m.id1 = uint32_t (it.id ()); + m.channel2 = -1; + ids.back ().push_back (m); + cerr << "adding match " << hex + << it.id () << dec + << " for string " + << it.text ()[stringIndex] + << " in channel " << chan->second + << '(' << chan->first << ")\n"; + } + } + } + else if ( + mfst[i].getEncodingScheme () == + IDManifest::ID2_SCHEME) + { + // 64 bit IDs are spread across two channels, with the least significant bits + // in the first channel (alphabetically) and the most significant bits in the second + // so process the channel set in pairs + + set::const_iterator chanLow = + mfst[i].getChannels ().begin (); + set::const_iterator end = + mfst[i].getChannels ().end (); + + while (chanLow != end) + { + set::const_iterator chanHigh = + chanLow; + ++chanHigh; + + if (chanHigh != end) + { + map::const_iterator + chanIdxLow = + channelToPos.find (*chanLow); + map::const_iterator + chanIdxHigh = + channelToPos.find (*chanHigh); + + if (chanIdxLow != channelToPos.end () && + chanIdxHigh != channelToPos.end ()) + { + // to match against specific channels, check at least one channel matches here + match m; + m.channel1 = chanIdxLow->second; + m.id1 = it.id () & 0xFFFFFFFF; + m.channel2 = chanIdxHigh->second; + m.id2 = it.id () >> 32; + ids.back ().push_back (m); + + cerr << "adding match " << hex + << it.id () << dec + << " for string " + << it.text ()[stringIndex] + << ": " << hex << m.id1 + << " in channel " << m.channel1 + << '(' << chanIdxLow->first + << "), " << hex << m.id2 + << " in channel " << m.channel2 + << '(' << chanIdxHigh->first + << ")\n"; + } + + ++chanLow; + if (chanLow != end) { ++chanLow; } + } + } + } + } + } + } + } + } + } +} diff --git a/src/examples/multipartExamples.cpp b/src/examples/multipartExamples.cpp index cce1a276c2..88b26cf184 100644 --- a/src/examples/multipartExamples.cpp +++ b/src/examples/multipartExamples.cpp @@ -314,6 +314,18 @@ void resizeDeepBuffers ( } } +template +void freeDeepBuffers (list> & channels) +{ + for (auto i = channels.begin (); i != channels.end (); i++) + { + Array2D & channel = *i; + for (int y = 0; y < channel.height (); y++) + for (int x = 0; x < channel.width (); x++) + delete[] channel[y][x]; + } +} + template void modifyChannels(list> & channels, T delta) { @@ -474,6 +486,10 @@ void modifyMultipart () outputPart.setFrameBuffer (frameBuffer); outputPart.writeTiles (0, outputPart.numXTiles () - 1, 0, outputPart.numYTiles () - 1); } + + freeDeepBuffers(intChannels); + freeDeepBuffers(halfChannels); + freeDeepBuffers(floatChannels); } } } diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index a315e444c5..8b4b5f0fa8 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -1,6 +1,8 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright (c) Contributors to the OpenEXR Project. +message(STATUS "Building OpenEXR libraries") + add_subdirectory( Iex ) add_subdirectory( IlmThread ) diff --git a/src/lib/OpenEXR/CMakeLists.txt b/src/lib/OpenEXR/CMakeLists.txt index c6759e7031..cc7d7ff1c9 100644 --- a/src/lib/OpenEXR/CMakeLists.txt +++ b/src/lib/OpenEXR/CMakeLists.txt @@ -8,6 +8,7 @@ openexr_define_library(OpenEXR ImfAutoArray.h ImfB44Compressor.h ImfCheckedArithmetic.h + ImfCompression.h ImfCompressor.h ImfDwaCompressor.h ImfDwaCompressorSimd.h @@ -43,6 +44,7 @@ openexr_define_library(OpenEXR ImfCompositeDeepScanLine.cpp ImfCompressionAttribute.cpp ImfCompressor.cpp + ImfCompression.cpp ImfConvert.cpp ImfCRgbaFile.cpp ImfDeepCompositing.cpp diff --git a/src/lib/OpenEXR/ImfB44Compressor.h b/src/lib/OpenEXR/ImfB44Compressor.h index ddeb880798..9a046df48c 100644 --- a/src/lib/OpenEXR/ImfB44Compressor.h +++ b/src/lib/OpenEXR/ImfB44Compressor.h @@ -29,10 +29,10 @@ class B44Compressor : public Compressor virtual ~B44Compressor (); - B44Compressor (const B44Compressor& other) = delete; + B44Compressor (const B44Compressor& other) = delete; B44Compressor& operator= (const B44Compressor& other) = delete; B44Compressor (B44Compressor&& other) = delete; - B44Compressor& operator= (B44Compressor&& other) = delete; + B44Compressor& operator= (B44Compressor&& other) = delete; virtual int numScanLines () const; diff --git a/src/lib/OpenEXR/ImfCRgbaFile.h b/src/lib/OpenEXR/ImfCRgbaFile.h index a83f951305..062f2f709a 100644 --- a/src/lib/OpenEXR/ImfCRgbaFile.h +++ b/src/lib/OpenEXR/ImfCRgbaFile.h @@ -80,6 +80,7 @@ typedef struct ImfRgba ImfRgba; #define IMF_B44A_COMPRESSION 7 #define IMF_DWAA_COMPRESSION 8 #define IMF_DWAB_COMPRESSION 9 +#define IMF_NUM_COMPRESSION_METHODS 10 /* ** Channels; values must be the same as in Imf::RgbaChannels. diff --git a/src/lib/OpenEXR/ImfCompression.cpp b/src/lib/OpenEXR/ImfCompression.cpp new file mode 100644 index 0000000000..97631779c5 --- /dev/null +++ b/src/lib/OpenEXR/ImfCompression.cpp @@ -0,0 +1,197 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) Contributors to the OpenEXR Project. +// + +#include "ImfNamespace.h" +#include "ImfCompression.h" +#include + +OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_ENTER + +/// Store codec properties so they may be queried in various places. +struct CompressionDesc +{ + std::string name; // short name + std::string desc; // method description + int numScanlines; // number of scanlines required + bool lossy; // true if lossy algorithm + bool deep; // true is capable of compressing deep data + + CompressionDesc ( + std::string _name, + std::string _desc, + int _scanlines, + bool _lossy, + bool _deep) + { + name = _name; + desc = _desc; + numScanlines = _scanlines; + lossy = _lossy; + deep = _deep; + } +}; + +// NOTE: IdToDesc order MUST match Imf::Compression enum. +// clang-format off +static const CompressionDesc IdToDesc[] = { + CompressionDesc ( + "none", + "no compression.", + 1, + false, + true), + CompressionDesc ( + "rle", + "run-length encoding.", + 1, + false, + true), + CompressionDesc ( + "zips", + "zlib compression, one scan line at a time.", + 1, + false, + true), + CompressionDesc ( + "zip", + "zlib compression, in blocks of 16 scan lines.", + 16, + false, + false), + CompressionDesc ( + "piz", + "piz-based wavelet compression, in blocks of 32 scan lines.", + 32, + false, + false), + CompressionDesc ( + "pxr24", + "lossy 24-bit float compression, in blocks of 16 scan lines.", + 16, + true, + false), + CompressionDesc ( + "b44", + "lossy 4-by-4 pixel block compression, fixed compression rate.", + 32, + true, + false), + CompressionDesc ( + "b44a", + "lossy 4-by-4 pixel block compression, flat fields are compressed more.", + 32, + true, + false), + CompressionDesc ( + "dwaa", + "lossy DCT based compression, in blocks of 32 scanlines. More efficient " + "for partial buffer access.", + 32, + true, + false), + CompressionDesc ( + "dwab", + "lossy DCT based compression, in blocks of 256 scanlines. More efficient " + "space wise and faster to decode full frames than DWAA_COMPRESSION.", + 256, + true, + false), +}; +// clang-format on + +// NOTE: CompressionNameToId order MUST match Imf::Compression enum. +static const std::map CompressionNameToId = { + {"no", Compression::NO_COMPRESSION}, + {"none", Compression::NO_COMPRESSION}, + {"rle", Compression::RLE_COMPRESSION}, + {"zips", Compression::ZIPS_COMPRESSION}, + {"zip", Compression::ZIP_COMPRESSION}, + {"piz", Compression::PIZ_COMPRESSION}, + {"pxr24", Compression::PXR24_COMPRESSION}, + {"b44", Compression::B44_COMPRESSION}, + {"b44a", Compression::B44A_COMPRESSION}, + {"dwaa", Compression::DWAA_COMPRESSION}, + {"dwab", Compression::DWAB_COMPRESSION}, +}; + +#define UNKNOWN_COMPRESSION_ID_MSG "INVALID COMPRESSION ID" + +/// Returns a codec ID's short name (lowercase). +void +getCompressionNameFromId (Compression id, std::string& name) +{ + if (id < NO_COMPRESSION || id >= NUM_COMPRESSION_METHODS) + name = UNKNOWN_COMPRESSION_ID_MSG; + name = IdToDesc[static_cast (id)].name; +} + +/// Returns a codec ID's short description (lowercase). +void +getCompressionDescriptionFromId (Compression id, std::string& desc) +{ + if (id < NO_COMPRESSION || id >= NUM_COMPRESSION_METHODS) + desc = UNKNOWN_COMPRESSION_ID_MSG; + desc = IdToDesc[static_cast (id)].name + ": " + + IdToDesc[static_cast (id)].desc; +} + +/// Returns the codec name's ID, NUM_COMPRESSION_METHODS if not found. +void +getCompressionIdFromName (const std::string& name, Compression& id) +{ + std::string lowercaseName (name); + for (auto& ch: lowercaseName) + ch = std::tolower (ch); + + auto it = CompressionNameToId.find (lowercaseName); + id = it != CompressionNameToId.end () + ? it->second + : Compression::NUM_COMPRESSION_METHODS; +} + +/// Return true if a compression id exists. +bool +isValidCompression (int id) +{ + return id >= NO_COMPRESSION && id < NUM_COMPRESSION_METHODS; +} + +/// Return a string enumerating all compression names, with a custom separator. +void +getCompressionNamesString (const std::string& separator, std::string& str) +{ + int i = 0; + for (; i < static_cast (NUM_COMPRESSION_METHODS) - 1; i++) + { + str += IdToDesc[i].name + separator; + } + str += IdToDesc[i].name; +} + +/// Return the number of scan lines expected by a given compression method. +int +getCompressionNumScanlines (Compression id) +{ + if (id < NO_COMPRESSION || id >= NUM_COMPRESSION_METHODS) return -1; + return IdToDesc[static_cast (id)].numScanlines; +} + +/// Return true is the compression method exists and doesn't preserve data integrity. +bool +isLossyCompression (Compression id) +{ + return id >= NO_COMPRESSION && id < NUM_COMPRESSION_METHODS && + IdToDesc[static_cast (id)].lossy; +} + +/// Return true is the compression method exists and supports deep data. +bool +isValidDeepCompression (Compression id) +{ + return id >= NO_COMPRESSION && id < NUM_COMPRESSION_METHODS && + IdToDesc[static_cast (id)].deep; +} + +OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_EXIT diff --git a/src/lib/OpenEXR/ImfCompression.h b/src/lib/OpenEXR/ImfCompression.h index e8f3386bf9..1ccffe5fd0 100644 --- a/src/lib/OpenEXR/ImfCompression.h +++ b/src/lib/OpenEXR/ImfCompression.h @@ -8,33 +8,39 @@ //----------------------------------------------------------------------------- // -// enum Compression +// enum Compression +// +// This file enumerates available compression methods and defines a simple API +// to query them. // //----------------------------------------------------------------------------- -#include "ImfExport.h" -#include "ImfNamespace.h" + +#include "ImfForward.h" +#include OPENEXR_IMF_INTERNAL_NAMESPACE_HEADER_ENTER +// All available compression methods. +// NOTE: Must be extended to add a new codec. Ids must be continuous. enum IMF_EXPORT_ENUM Compression { - NO_COMPRESSION = 0, // no compression + NO_COMPRESSION = 0, // no compression. - RLE_COMPRESSION = 1, // run length encoding + RLE_COMPRESSION = 1, // run length encoding. - ZIPS_COMPRESSION = 2, // zlib compression, one scan line at a time + ZIPS_COMPRESSION = 2, // zlib compression, one scan line at a time. - ZIP_COMPRESSION = 3, // zlib compression, in blocks of 16 scan lines + ZIP_COMPRESSION = 3, // zlib compression, in blocks of 16 scan lines. - PIZ_COMPRESSION = 4, // piz-based wavelet compression + PIZ_COMPRESSION = 4, // piz-based wavelet compression. PXR24_COMPRESSION = 5, // lossy 24-bit float compression B44_COMPRESSION = 6, // lossy 4-by-4 pixel block compression, - // fixed compression rate + // fixed compression rate. B44A_COMPRESSION = 7, // lossy 4-by-4 pixel block compression, - // flat fields are compressed more + // flat fields are compressed more. DWAA_COMPRESSION = 8, // lossy DCT based compression, in blocks // of 32 scanlines. More efficient for partial @@ -47,9 +53,36 @@ enum IMF_EXPORT_ENUM Compression ZSTD_COMPRESSION = 10, - NUM_COMPRESSION_METHODS // number of different compression methods + NUM_COMPRESSION_METHODS // number of different compression methods. }; +/// Returns a codec ID's short name (lowercase). +IMF_EXPORT void getCompressionNameFromId (Compression id, std::string& name); + +/// Returns a codec ID's short description (lowercase). +IMF_EXPORT void +getCompressionDescriptionFromId (Compression id, std::string& desc); + +/// Returns the codec name's ID, NUM_COMPRESSION_METHODS if not found. +IMF_EXPORT void +getCompressionIdFromName (const std::string& name, Compression& id); + +/// Return true if a compression id exists. +IMF_EXPORT bool isValidCompression (int id); + +/// Return a string enumerating all compression names, with a custom separator. +IMF_EXPORT void +getCompressionNamesString (const std::string& separator, std::string& in); + +/// Return the number of scan lines expected by a given compression method. +IMF_EXPORT int getCompressionNumScanlines (Compression id); + +/// Return true is the compression method exists and doesn't preserves data integrity. +IMF_EXPORT bool isLossyCompression (Compression id); + +/// Return true is the compression method exists and supports deep data. +IMF_EXPORT bool isValidDeepCompression (Compression id); + /// Controls the default zip compression level used. Zip is used for /// the 2 zip levels as well as some modes of the DWAA/B compression. IMF_EXPORT void setDefaultZipCompressionLevel (int level); diff --git a/src/lib/OpenEXR/ImfCompressionAttribute.cpp b/src/lib/OpenEXR/ImfCompressionAttribute.cpp index a443eba8bb..54b0fd1a8f 100644 --- a/src/lib/OpenEXR/ImfCompressionAttribute.cpp +++ b/src/lib/OpenEXR/ImfCompressionAttribute.cpp @@ -47,18 +47,13 @@ CompressionAttribute::readValueFrom ( Xdr::read (is, tmp); // - // prevent invalid values being written to Compressin enum + // prevent invalid values being written to Compression enum // by forcing all unknown types to NUM_COMPRESSION_METHODS which is also an invalid // pixel type, but can be used as a PixelType enum value // (Header::sanityCheck will throw an exception when files with invalid Compression types are read) // - if (tmp != NO_COMPRESSION && tmp != RLE_COMPRESSION && - tmp != ZIPS_COMPRESSION && tmp != ZIP_COMPRESSION && - tmp != PIZ_COMPRESSION && tmp != PXR24_COMPRESSION && - tmp != B44_COMPRESSION && tmp != B44A_COMPRESSION && - tmp != DWAA_COMPRESSION && tmp != DWAB_COMPRESSION && - tmp != ZSTD_COMPRESSION) + if (!isValidCompression(tmp)) { tmp = NUM_COMPRESSION_METHODS; } diff --git a/src/lib/OpenEXR/ImfCompressor.cpp b/src/lib/OpenEXR/ImfCompressor.cpp index db51021dc6..147de52683 100644 --- a/src/lib/OpenEXR/ImfCompressor.cpp +++ b/src/lib/OpenEXR/ImfCompressor.cpp @@ -9,11 +9,11 @@ // //----------------------------------------------------------------------------- +#include "ImfCheckedArithmetic.h" +#include "ImfNamespace.h" #include "ImfCompressor.h" #include "ImfB44Compressor.h" -#include "ImfCheckedArithmetic.h" #include "ImfDwaCompressor.h" -#include "ImfNamespace.h" #include "ImfPizCompressor.h" #include "ImfPxr24Compressor.h" #include "ImfRleCompressor.h" @@ -51,59 +51,15 @@ Compressor::uncompressTile ( return uncompress (inPtr, inSize, range.min.y, outPtr); } -bool -isValidCompression (Compression c) -{ - switch (c) - { - case NO_COMPRESSION: - case RLE_COMPRESSION: - case ZIPS_COMPRESSION: - case ZIP_COMPRESSION: - case PIZ_COMPRESSION: - case PXR24_COMPRESSION: - case B44_COMPRESSION: - case B44A_COMPRESSION: - case DWAA_COMPRESSION: - case DWAB_COMPRESSION: - case ZSTD_COMPRESSION: return true; - - default: return false; - } -} - -bool -isLossyCompression (Compression c) -{ - switch (c) - { - case B44_COMPRESSION: - case B44A_COMPRESSION: - case DWAA_COMPRESSION: - case DWAB_COMPRESSION: return true; - default: return false; - } -} - -bool -isValidDeepCompression (Compression c) -{ - switch (c) - { - case NO_COMPRESSION: - case RLE_COMPRESSION: - case ZIPS_COMPRESSION: - case ZSTD_COMPRESSION: return true; - default: return false; - } -} - Compressor* newCompressor (Compression c, size_t maxScanLineSize, const Header& hdr) { + // clang-format off switch (c) { - case RLE_COMPRESSION: return new RleCompressor (hdr, maxScanLineSize); + case RLE_COMPRESSION: + + return new RleCompressor (hdr, maxScanLineSize); case ZIPS_COMPRESSION: @@ -149,6 +105,7 @@ newCompressor (Compression c, size_t maxScanLineSize, const Header& hdr) return new ZstdCompressor (hdr); default: return 0; } + // clang-format on } // for a given compression type, return the number of scanlines @@ -157,28 +114,17 @@ newCompressor (Compression c, size_t maxScanLineSize, const Header& hdr) int numLinesInBuffer (Compression comp) { - switch (comp) - { - case NO_COMPRESSION: - case RLE_COMPRESSION: - case ZIPS_COMPRESSION: return 1; - case ZIP_COMPRESSION: return 16; - case PIZ_COMPRESSION: return 32; - case PXR24_COMPRESSION: return 16; - case B44_COMPRESSION: - case B44A_COMPRESSION: - case DWAA_COMPRESSION: return 32; - case ZSTD_COMPRESSION: return (int)exr_get_zstd_lines_per_chunk(); - case DWAB_COMPRESSION: return 256; - - default: throw IEX_NAMESPACE::ArgExc ("Unknown compression type"); - } + int numScanlines = getCompressionNumScanlines (comp); + if (numScanlines < 1) + throw IEX_NAMESPACE::ArgExc ("Unknown compression type"); + return numScanlines; } Compressor* newTileCompressor ( Compression c, size_t tileLineSize, size_t numTileLines, const Header& hdr) { + // clang-format off switch (c) { case RLE_COMPRESSION: @@ -227,6 +173,7 @@ newTileCompressor ( default: return 0; } + // clang-format on } OPENEXR_IMF_INTERNAL_NAMESPACE_SOURCE_EXIT diff --git a/src/lib/OpenEXR/ImfCompressor.h b/src/lib/OpenEXR/ImfCompressor.h index 9abd95e5a0..efb6cbfe53 100644 --- a/src/lib/OpenEXR/ImfCompressor.h +++ b/src/lib/OpenEXR/ImfCompressor.h @@ -163,28 +163,6 @@ class IMF_EXPORT_TYPE Compressor const Header& _header; }; -//-------------------------------------- -// Test if c is a valid compression type -//-------------------------------------- - -IMF_EXPORT -bool isValidCompression (Compression c); - -//-------------------------------------- -// Test if c is valid for deep data -//-------------------------------------- - -IMF_EXPORT -bool isValidDeepCompression (Compression c); - -//--------------------------------------- -// Return true for compression types which -// do not guarantee that HALF type values -// are preserved precisely -//--------------------------------------- -IMF_EXPORT -bool isLossyCompression (Compression c); - //----------------------------------------------------------------- // Construct a Compressor for compression type c: // diff --git a/src/lib/OpenEXR/ImfDwaCompressor.h b/src/lib/OpenEXR/ImfDwaCompressor.h index dca9f6834b..43e38ab381 100644 --- a/src/lib/OpenEXR/ImfDwaCompressor.h +++ b/src/lib/OpenEXR/ImfDwaCompressor.h @@ -41,10 +41,10 @@ class DwaCompressor : public Compressor virtual ~DwaCompressor (); - DwaCompressor (const DwaCompressor& other) = delete; + DwaCompressor (const DwaCompressor& other) = delete; DwaCompressor& operator= (const DwaCompressor& other) = delete; DwaCompressor (DwaCompressor&& other) = delete; - DwaCompressor& operator= (DwaCompressor&& other) = delete; + DwaCompressor& operator= (DwaCompressor&& other) = delete; virtual int numScanLines () const; diff --git a/src/lib/OpenEXR/ImfMultiPartInputFile.cpp b/src/lib/OpenEXR/ImfMultiPartInputFile.cpp index b6fb9aeb12..c82f7a9a97 100644 --- a/src/lib/OpenEXR/ImfMultiPartInputFile.cpp +++ b/src/lib/OpenEXR/ImfMultiPartInputFile.cpp @@ -545,24 +545,11 @@ MultiPartInputFile::Data::chunkOffsetReconstruction ( else { tileOffsets[i] = NULL; - // (TODO) fix this so that it doesn't need to be revised for future compression types. - switch (parts[i]->header.compression ()) - { - case ZSTD_COMPRESSION: rowsizes[i] = (int)exr_get_zstd_lines_per_chunk(); break; - case DWAB_COMPRESSION: rowsizes[i] = 256; break; - case PIZ_COMPRESSION: - case B44_COMPRESSION: - case B44A_COMPRESSION: - case DWAA_COMPRESSION: rowsizes[i] = 32; break; - case ZIP_COMPRESSION: - case PXR24_COMPRESSION: rowsizes[i] = 16; break; - case ZIPS_COMPRESSION: - case RLE_COMPRESSION: - case NO_COMPRESSION: rowsizes[i] = 1; break; - default: - throw (IEX_NAMESPACE::ArgExc ( - "Unknown compression method in chunk offset reconstruction")); - } + rowsizes[i] = + getCompressionNumScanlines (parts[i]->header.compression ()); + if (rowsizes[i] < 1) + throw (IEX_NAMESPACE::ArgExc ( + "Unknown compression method in chunk offset reconstruction")); } } diff --git a/src/lib/OpenEXR/ImfOptimizedPixelReading.h b/src/lib/OpenEXR/ImfOptimizedPixelReading.h index f6dd300417..f2adfad5e7 100644 --- a/src/lib/OpenEXR/ImfOptimizedPixelReading.h +++ b/src/lib/OpenEXR/ImfOptimizedPixelReading.h @@ -24,7 +24,7 @@ class OptimizationMode public: bool _optimizable; int _ySampling; - OptimizationMode () : _optimizable (false) {} + OptimizationMode () : _optimizable (false), _ySampling (0) {} }; # ifdef IMF_HAVE_SSE2 diff --git a/src/lib/OpenEXR/ImfPizCompressor.h b/src/lib/OpenEXR/ImfPizCompressor.h index af5d442715..47ac5194a4 100644 --- a/src/lib/OpenEXR/ImfPizCompressor.h +++ b/src/lib/OpenEXR/ImfPizCompressor.h @@ -26,10 +26,10 @@ class PizCompressor : public Compressor virtual ~PizCompressor (); - PizCompressor (const PizCompressor& other) = delete; + PizCompressor (const PizCompressor& other) = delete; PizCompressor& operator= (const PizCompressor& other) = delete; PizCompressor (PizCompressor&& other) = delete; - PizCompressor& operator= (PizCompressor&& other) = delete; + PizCompressor& operator= (PizCompressor&& other) = delete; virtual int numScanLines () const; diff --git a/src/lib/OpenEXR/ImfPxr24Compressor.h b/src/lib/OpenEXR/ImfPxr24Compressor.h index 1cc178e8ed..42bdf1cab9 100644 --- a/src/lib/OpenEXR/ImfPxr24Compressor.h +++ b/src/lib/OpenEXR/ImfPxr24Compressor.h @@ -24,10 +24,10 @@ class Pxr24Compressor : public Compressor virtual ~Pxr24Compressor (); - Pxr24Compressor (const Pxr24Compressor& other) = delete; + Pxr24Compressor (const Pxr24Compressor& other) = delete; Pxr24Compressor& operator= (const Pxr24Compressor& other) = delete; Pxr24Compressor (Pxr24Compressor&& other) = delete; - Pxr24Compressor& operator= (Pxr24Compressor&& other) = delete; + Pxr24Compressor& operator= (Pxr24Compressor&& other) = delete; virtual int numScanLines () const; diff --git a/src/lib/OpenEXR/ImfRleCompressor.h b/src/lib/OpenEXR/ImfRleCompressor.h index dba75566e3..714527ae82 100644 --- a/src/lib/OpenEXR/ImfRleCompressor.h +++ b/src/lib/OpenEXR/ImfRleCompressor.h @@ -24,10 +24,10 @@ class RleCompressor : public Compressor RleCompressor (const Header& hdr, size_t maxScanLineSize); virtual ~RleCompressor (); - RleCompressor (const RleCompressor& other) = delete; + RleCompressor (const RleCompressor& other) = delete; RleCompressor& operator= (const RleCompressor& other) = delete; RleCompressor (RleCompressor&& other) = delete; - RleCompressor& operator= (RleCompressor&& other) = delete; + RleCompressor& operator= (RleCompressor&& other) = delete; virtual int numScanLines () const; diff --git a/src/lib/OpenEXRCore/chunk.c b/src/lib/OpenEXRCore/chunk.c index 304a402ccc..987408fdac 100644 --- a/src/lib/OpenEXRCore/chunk.c +++ b/src/lib/OpenEXRCore/chunk.c @@ -454,10 +454,12 @@ reconstruct_chunk_table ( uint64_t* chunktable) { exr_result_t rv = EXR_ERR_SUCCESS; + exr_result_t firstfailrv = EXR_ERR_SUCCESS; uint64_t offset_start, chunk_start, max_offset; uint64_t* curctable; const struct _internal_exr_part* curpart = NULL; int found_ci, computed_ci, partnum = 0; + size_t chunkbytes; curpart = ctxt->parts[ctxt->num_parts - 1]; offset_start = curpart->chunk_table_offset; @@ -492,6 +494,11 @@ reconstruct_chunk_table ( if (rv != EXR_ERR_SUCCESS) return rv; } + chunkbytes = (size_t)part->chunk_count * sizeof(uint64_t); + curctable = (uint64_t*) ctxt->alloc_fn (chunkbytes); + if (!curctable) return EXR_ERR_OUT_OF_MEMORY; + + memset (curctable, 0, chunkbytes); for (int ci = 0; ci < part->chunk_count; ++ci) { if (chunktable[ci] >= offset_start && chunktable[ci] < max_offset) @@ -503,21 +510,37 @@ reconstruct_chunk_table ( if (part->lineorder == EXR_LINEORDER_DECREASING_Y) computed_ci = part->chunk_count - (ci + 1); found_ci = computed_ci; + rv = read_and_validate_chunk_leader ( ctxt, part, partnum, chunk_start, &found_ci, &offset_start); - if (rv != EXR_ERR_SUCCESS) return rv; + if (rv != EXR_ERR_SUCCESS) + { + chunk_start = 0; + if (firstfailrv == EXR_ERR_SUCCESS) firstfailrv = rv; + } // scanlines can be more strict about the ordering if (part->storage_mode == EXR_STORAGE_SCANLINE || part->storage_mode == EXR_STORAGE_DEEP_SCANLINE) { - if (computed_ci != found_ci) return EXR_ERR_BAD_CHUNK_LEADER; + if (computed_ci != found_ci) + { + chunk_start = 0; + if (firstfailrv == EXR_ERR_SUCCESS) + firstfailrv = EXR_ERR_BAD_CHUNK_LEADER; + } } - chunktable[found_ci] = chunk_start; + if (found_ci >= 0 && found_ci < part->chunk_count) + { + if (curctable[found_ci] == 0) + curctable[found_ci] = chunk_start; + } } + memcpy (chunktable, curctable, chunkbytes); + ctxt->free_fn (curctable); - return rv; + return firstfailrv; } static exr_result_t @@ -566,10 +589,9 @@ extract_chunk_table ( if (rv != EXR_ERR_SUCCESS) { ctxt->free_fn (ctable); - return rv; + ctable = (uint64_t *) UINTPTR_MAX; } - - if (!ctxt->disable_chunk_reconstruct) + else if (!ctxt->disable_chunk_reconstruct) { // could convert table all at once, but need to check if the // file is incomplete (i.e. crashed during write and didn't @@ -590,13 +612,19 @@ extract_chunk_table ( // something similar, except when in strict mode, we // will fail with a corrupt chunk immediately. rv = reconstruct_chunk_table (ctxt, part, ctable); - if (rv != EXR_ERR_SUCCESS && ctxt->strict_header) + if (rv != EXR_ERR_SUCCESS) { - ctxt->free_fn (ctable); - return ctxt->report_error ( - ctxt, - EXR_ERR_BAD_CHUNK_LEADER, - "Incomplete / corrupt chunk table, unable to reconstruct"); + if (ctxt->strict_header) + { + ctxt->free_fn (ctable); + ctable = (uint64_t *) UINTPTR_MAX; + rv = ctxt->report_error ( + ctxt, + EXR_ERR_BAD_CHUNK_LEADER, + "Incomplete / corrupt chunk table, unable to reconstruct"); + } + else + rv = EXR_ERR_SUCCESS; } } } @@ -609,7 +637,8 @@ extract_chunk_table ( &eptr, nptr)) { - ctxt->free_fn (ctable); + if (nptr != UINTPTR_MAX) + ctxt->free_fn (ctable); ctable = (uint64_t*) eptr; if (ctable == NULL) return ctxt->standard_error (ctxt, EXR_ERR_OUT_OF_MEMORY); @@ -617,7 +646,7 @@ extract_chunk_table ( } *chunktable = ctable; - return EXR_ERR_SUCCESS; + return ((uintptr_t)ctable) == UINTPTR_MAX ? EXR_ERR_BAD_CHUNK_LEADER : EXR_ERR_SUCCESS; } /**************************************/ diff --git a/src/lib/OpenEXRCore/decoding.c b/src/lib/OpenEXRCore/decoding.c index c7db79f755..a279441c85 100644 --- a/src/lib/OpenEXRCore/decoding.c +++ b/src/lib/OpenEXRCore/decoding.c @@ -292,6 +292,7 @@ default_decompress_chunk (exr_decode_pipeline_t* decode) uint64_t sampsize = (((uint64_t) decode->chunk.width) * ((uint64_t) decode->chunk.height)); + sampsize *= sizeof (int32_t); rv = decompress_data ( @@ -344,7 +345,7 @@ unpack_sample_table ( exr_result_t rv = EXR_ERR_SUCCESS; int32_t w = decode->chunk.width; int32_t h = decode->chunk.height; - int32_t totsamp = 0; + uint64_t totsamp = 0; int32_t* samptable = decode->sample_count_table; size_t combSampSize = 0; @@ -355,42 +356,52 @@ unpack_sample_table ( { for (int32_t y = 0; y < h; ++y) { + int32_t *cursampline = samptable + y * w; int32_t prevsamp = 0; for (int32_t x = 0; x < w; ++x) { int32_t nsamps = - (int32_t) one_to_native32 ((uint32_t) samptable[y * w + x]); - if (nsamps < 0) return EXR_ERR_INVALID_SAMPLE_DATA; - samptable[y * w + x] = nsamps - prevsamp; - prevsamp = nsamps; + (int32_t) one_to_native32 ((uint32_t) cursampline[x]); + // not monotonic, violation + if (nsamps < prevsamp) return EXR_ERR_INVALID_SAMPLE_DATA; + cursampline[x] = nsamps - prevsamp; + prevsamp = nsamps; } - totsamp += prevsamp; + totsamp += (uint64_t)prevsamp; } - samptable[w * h] = totsamp; + if (totsamp >= (uint64_t)INT32_MAX) + return EXR_ERR_INVALID_SAMPLE_DATA; + samptable[w * h] = (int32_t)totsamp; } else { for (int32_t y = 0; y < h; ++y) { + int32_t *cursampline = samptable + y * w; int32_t prevsamp = 0; for (int32_t x = 0; x < w; ++x) { int32_t nsamps = - (int32_t) one_to_native32 ((uint32_t) samptable[y * w + x]); - if (nsamps < 0) return EXR_ERR_INVALID_SAMPLE_DATA; - samptable[y * w + x] = nsamps; - prevsamp = nsamps; + (int32_t) one_to_native32 ((uint32_t) cursampline[x]); + // not monotonic, violation + if (nsamps < prevsamp) return EXR_ERR_INVALID_SAMPLE_DATA; + + cursampline[x] = nsamps; + prevsamp = nsamps; } - totsamp += prevsamp; + + totsamp += (uint64_t)prevsamp; } + if (totsamp >= (uint64_t)INT32_MAX) + return EXR_ERR_INVALID_SAMPLE_DATA; } - if (totsamp < 0 || - (((uint64_t) totsamp) * combSampSize) > decode->chunk.unpacked_size) + if ((totsamp * combSampSize) > decode->chunk.unpacked_size) { rv = pctxt->report_error ( pctxt, EXR_ERR_INVALID_SAMPLE_DATA, "Corrupt sample count table"); } + return rv; } @@ -661,14 +672,34 @@ exr_decoding_run ( (part->storage_mode == EXR_STORAGE_DEEP_SCANLINE || part->storage_mode == EXR_STORAGE_DEEP_TILED)) { + if (part->comp_type == EXR_COMPRESSION_NONE && + decode->sample_count_table != decode->packed_sample_count_table) + { + /* happens when we're requested to pack to 'individual' mode */ + if (decode->sample_count_alloc_size < decode->chunk.sample_count_table_size) + return EXR_ERR_OUT_OF_MEMORY; + if (decode->chunk.sample_count_table_size > 0) + { + memcpy (decode->sample_count_table, + decode->packed_sample_count_table, + decode->chunk.sample_count_table_size); + } + else + { + memset (decode->sample_count_table, 0, decode->sample_count_alloc_size); + } + } + rv = unpack_sample_table (pctxt, decode); if ((decode->decode_flags & EXR_DECODE_SAMPLE_DATA_ONLY)) return rv; - } - if (rv != EXR_ERR_SUCCESS) - return pctxt->report_error ( - pctxt, rv, "Decode pipeline unable to unpack deep sample table"); + if (rv != EXR_ERR_SUCCESS) + return pctxt->report_error ( + pctxt, + rv, + "Decode pipeline unable to unpack deep sample table"); + } if (rv == EXR_ERR_SUCCESS && decode->realloc_nonimage_data_fn) rv = decode->realloc_nonimage_data_fn (decode); @@ -678,11 +709,14 @@ exr_decoding_run ( rv, "Decode pipeline unable to realloc deep sample table info"); - if (rv == EXR_ERR_SUCCESS && decode->unpack_and_convert_fn) - rv = decode->unpack_and_convert_fn (decode); - if (rv != EXR_ERR_SUCCESS) - return pctxt->report_error ( - pctxt, rv, "Decode pipeline unable to unpack and convert data"); + if (decode->chunk.unpacked_size > 0) + { + if (rv == EXR_ERR_SUCCESS && decode->unpack_and_convert_fn) + rv = decode->unpack_and_convert_fn (decode); + if (rv != EXR_ERR_SUCCESS) + return pctxt->report_error ( + pctxt, rv, "Decode pipeline unable to unpack and convert data"); + } return rv; } @@ -703,6 +737,10 @@ exr_decoding_destroy (exr_const_context_t ctxt, exr_decode_pipeline_t* decode) decode->unpacked_alloc_size == 0) decode->unpacked_buffer = NULL; + if (decode->sample_count_table == decode->packed_sample_count_table && + decode->sample_count_alloc_size == 0) + decode->sample_count_table = NULL; + internal_decode_free_buffer ( decode, EXR_TRANSCODE_BUFFER_PACKED, @@ -723,16 +761,17 @@ exr_decoding_destroy (exr_const_context_t ctxt, exr_decode_pipeline_t* decode) EXR_TRANSCODE_BUFFER_SCRATCH2, &(decode->scratch_buffer_2), &(decode->scratch_alloc_size_2)); - internal_decode_free_buffer ( - decode, - EXR_TRANSCODE_BUFFER_PACKED_SAMPLES, - &(decode->packed_sample_count_table), - &(decode->packed_sample_count_alloc_size)); + internal_decode_free_buffer ( decode, EXR_TRANSCODE_BUFFER_SAMPLES, (void**) &(decode->sample_count_table), &(decode->sample_count_alloc_size)); + internal_decode_free_buffer ( + decode, + EXR_TRANSCODE_BUFFER_PACKED_SAMPLES, + &(decode->packed_sample_count_table), + &(decode->packed_sample_count_alloc_size)); *decode = nil; } return EXR_ERR_SUCCESS; diff --git a/src/lib/OpenEXRCore/internal_dwa_compressor.h b/src/lib/OpenEXRCore/internal_dwa_compressor.h index 03ad606695..3e986950b4 100644 --- a/src/lib/OpenEXRCore/internal_dwa_compressor.h +++ b/src/lib/OpenEXRCore/internal_dwa_compressor.h @@ -251,20 +251,11 @@ DwaCompressor_compress (DwaCompressor* me) uint8_t* outDataPtr; uint8_t* inDataPtr; - // Starting with 2, we write the channel + // Starting with DWA v2, we write the channel // classification rules into the file - if (fileVersion < 2) - { - me->_channelRules = sLegacyChannelRules; - me->_channelRuleCount = - sizeof (sLegacyChannelRules) / sizeof (Classifier); - } - else - { - me->_channelRules = sDefaultChannelRules; - me->_channelRuleCount = - sizeof (sDefaultChannelRules) / sizeof (Classifier); - } + me->_channelRules = sDefaultChannelRules; + me->_channelRuleCount = + sizeof (sDefaultChannelRules) / sizeof (Classifier); rv = DwaCompressor_initializeBuffers (me, &outBufferSize); @@ -1047,6 +1038,7 @@ DwaCompressor_uncompress ( me->alloc_fn, me->free_fn, &(cd->_dctData), outBufferEnd); if (rv != EXR_ERR_SUCCESS) return rv; + cd->_dctData._type = chan->data_type; outBufferEnd += chan->width * chan->bytes_per_element; } } @@ -1707,12 +1699,23 @@ DwaCompressor_setupChannelData (DwaCompressor* me) cd->planarUncRle[0] = cd->planarUncBuffer; cd->planarUncRleEnd[0] = cd->planarUncRle[0]; - for (int byte = 1; byte < curc->bytes_per_element; ++byte) + if (!cd->planarUncBuffer) { - cd->planarUncRle[byte] = - cd->planarUncRle[byte - 1] + curc->width * curc->height; + for (int byte = 1; byte < curc->bytes_per_element; ++byte) + { + cd->planarUncRle[byte] = 0; + cd->planarUncRleEnd[byte] = 0; + } + } + else + { + for (int byte = 1; byte < curc->bytes_per_element; ++byte) + { + cd->planarUncRle[byte] = + cd->planarUncRle[byte - 1] + curc->width * curc->height; - cd->planarUncRleEnd[byte] = cd->planarUncRle[byte]; + cd->planarUncRleEnd[byte] = cd->planarUncRle[byte]; + } } cd->planarUncType = (exr_pixel_type_t) curc->data_type; diff --git a/src/lib/OpenEXRCore/internal_huf.c b/src/lib/OpenEXRCore/internal_huf.c index b6c7b0077c..74d7124cd6 100644 --- a/src/lib/OpenEXRCore/internal_huf.c +++ b/src/lib/OpenEXRCore/internal_huf.c @@ -1007,13 +1007,23 @@ readUInt (const uint8_t* b) #ifdef __APPLE__ # include -# define READ64(c) OSSwapInt64 (*(const uint64_t*) (c)) +# define SWAP64(c) OSSwapInt64 (c) #elif defined(linux) # include -# define READ64(c) bswap_64 (*(const uint64_t*) (c)) +# define SWAP64(c) bswap_64 (c) #elif defined(_MSC_VER) # include -# define READ64(c) _byteswap_uint64 (*(const uint64_t*) (c)) +# define SWAP64(c) _byteswap_uint64 (c) +#endif + +#ifdef SWAP64 +static inline uint64_t READ64(const uint8_t *src) +{ + uint64_t v; + // unaligned reads are UB + memcpy (&v, src, sizeof(uint64_t)); + return SWAP64 (v); +} #else # define READ64(c) \ ((uint64_t) (c)[0] << 56) | ((uint64_t) (c)[1] << 48) | \ diff --git a/src/lib/OpenEXRCore/internal_structs.c b/src/lib/OpenEXRCore/internal_structs.c index ef39ca46d6..477e2872e6 100644 --- a/src/lib/OpenEXRCore/internal_structs.c +++ b/src/lib/OpenEXRCore/internal_structs.c @@ -174,7 +174,7 @@ internal_exr_destroy_part ( ctable = (uint64_t*) atomic_load (&(cur->chunk_table)); atomic_store (&(cur->chunk_table), (uintptr_t) (0)); #endif - if (ctable) dofree (ctable); + if (ctable && ((uintptr_t)ctable) != UINTPTR_MAX) dofree (ctable); } /**************************************/ diff --git a/src/lib/OpenEXRCore/openexr_version.h b/src/lib/OpenEXRCore/openexr_version.h index 11f5020e72..ee93306351 100644 --- a/src/lib/OpenEXRCore/openexr_version.h +++ b/src/lib/OpenEXRCore/openexr_version.h @@ -9,7 +9,7 @@ # define INCLUDED_OPENEXR_VERSION_H # define OPENEXR_VERSION_MAJOR 3 -# define OPENEXR_VERSION_MINOR 2 +# define OPENEXR_VERSION_MINOR 3 # define OPENEXR_VERSION_PATCH 0 #endif diff --git a/src/lib/OpenEXRCore/unpack.c b/src/lib/OpenEXRCore/unpack.c index b88c98974c..1324508c5b 100644 --- a/src/lib/OpenEXRCore/unpack.c +++ b/src/lib/OpenEXRCore/unpack.c @@ -1196,9 +1196,10 @@ generic_unpack_deep_pointers (exr_decode_pipeline_t* decode) if (outpix) { uint8_t* cdata = outpix; + UNPACK_SAMPLES (samps) } - srcbuffer += bpc * samps; + srcbuffer += ((size_t) bpc) * ((size_t) samps); } } sampbuffer += w; @@ -1242,12 +1243,14 @@ generic_unpack_deep (exr_decode_pipeline_t* decode) } else prevsamps = sampbuffer[w - 1]; + srcbuffer += ((size_t) bpc) * ((size_t) prevsamps); if (incr_tot) totsamps += (size_t) prevsamps; continue; } + cdata += totsamps * ((size_t) ubpc); for (int x = 0; x < w; ++x) @@ -1263,7 +1266,7 @@ generic_unpack_deep (exr_decode_pipeline_t* decode) UNPACK_SAMPLES (samps) - srcbuffer += bpc * samps; + srcbuffer += ((size_t) bpc) * ((size_t) samps); if (incr_tot) totsamps += (size_t) samps; } } @@ -1301,7 +1304,7 @@ internal_exr_match_decode ( if (isdeep) { - if ((decode->decode_flags & EXR_DECODE_SAMPLE_COUNTS_AS_INDIVIDUAL)) + if ((decode->decode_flags & EXR_DECODE_NON_IMAGE_DATA_AS_POINTERS)) return &generic_unpack_deep_pointers; return &generic_unpack_deep; } diff --git a/src/lib/OpenEXRUtil/ImfCheckFile.cpp b/src/lib/OpenEXRUtil/ImfCheckFile.cpp index 82f318a92f..b9de50268d 100644 --- a/src/lib/OpenEXRUtil/ImfCheckFile.cpp +++ b/src/lib/OpenEXRUtil/ImfCheckFile.cpp @@ -1200,13 +1200,92 @@ runChecks (T& source, bool reduceMemory, bool reduceTime) return threw; } +// This is not entirely needed in that the chunk info has the +// total unpacked_size field which can be used for allocation +// but this adds an additional point to use when debugging issues. +static exr_result_t +realloc_deepdata(exr_decode_pipeline_t* decode) +{ + int32_t w = decode->chunk.width; + int32_t h = decode->chunk.height; + uint64_t totsamps = 0, bytes = 0; + const int32_t *sampbuffer = decode->sample_count_table; + std::vector* ud = static_cast*>( + decode->decoding_user_data); + + if ( ! ud ) + { + for (int c = 0; c < decode->channel_count; c++) + { + exr_coding_channel_info_t& outc = decode->channels[c]; + outc.decode_to_ptr = NULL; + outc.user_pixel_stride = outc.user_bytes_per_element; + outc.user_line_stride = 0; + } + return EXR_ERR_SUCCESS; + } + + if ((decode->decode_flags & + EXR_DECODE_SAMPLE_COUNTS_AS_INDIVIDUAL)) + { + for (int32_t y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + totsamps += sampbuffer[x]; + sampbuffer += w; + } + } + else + { + for (int32_t y = 0; y < h; ++y) + totsamps += sampbuffer[y*w + w - 1]; + } + + for (int c = 0; c < decode->channel_count; c++) + { + exr_coding_channel_info_t& outc = decode->channels[c]; + bytes += totsamps * outc.user_bytes_per_element; + } + + if (bytes == 0 || bytes >= gMaxBytesPerDeepScanline) + { + for (int c = 0; c < decode->channel_count; c++) + { + exr_coding_channel_info_t& outc = decode->channels[c]; + outc.decode_to_ptr = NULL; + outc.user_pixel_stride = outc.user_bytes_per_element; + outc.user_line_stride = 0; + } + return EXR_ERR_SUCCESS; + } + + if (ud->size () < bytes) + { + ud->resize (bytes); + if (ud->capacity() < bytes) + return EXR_ERR_OUT_OF_MEMORY; + } + + uint8_t* dptr = &((*ud)[0]); + for (int c = 0; c < decode->channel_count; c++) + { + exr_coding_channel_info_t& outc = decode->channels[c]; + outc.decode_to_ptr = dptr; + outc.user_pixel_stride = outc.user_bytes_per_element; + outc.user_line_stride = 0; + + dptr += totsamps * (uint64_t) outc.user_bytes_per_element; + } + return EXR_ERR_SUCCESS; +} + //////////////////////////////////////// bool readCoreScanlinePart ( exr_context_t f, int part, bool reduceMemory, bool reduceTime) { - exr_result_t rv; + exr_result_t rv, frv; exr_attr_box2i_t datawin; rv = exr_get_data_window (f, part, &datawin); if (rv != EXR_ERR_SUCCESS) return true; @@ -1224,6 +1303,8 @@ readCoreScanlinePart ( rv = exr_get_scanlines_per_chunk (f, part, &lines_per_chunk); if (rv != EXR_ERR_SUCCESS) return true; + frv = rv; + for (uint64_t chunk = 0; chunk < height; chunk += lines_per_chunk) { exr_chunk_info_t cinfo = {0}; @@ -1232,6 +1313,7 @@ readCoreScanlinePart ( rv = exr_read_scanline_chunk_info (f, part, y, &cinfo); if (rv != EXR_ERR_SUCCESS) { + frv = rv; if (reduceTime) break; continue; } @@ -1253,19 +1335,32 @@ readCoreScanlinePart ( (uint64_t) lines_per_chunk; } - // TODO: check we are supposed to multiple by lines per chunk above doread = true; - if (reduceMemory && bytes >= gMaxBytesPerScanline) doread = false; + if (reduceMemory && bytes >= gMaxBytesPerScanline) + doread = false; - if (doread) imgdata.resize (bytes); + if (cinfo.type == EXR_STORAGE_DEEP_SCANLINE) + { + decoder.decoding_user_data = &imgdata; + decoder.realloc_nonimage_data_fn = &realloc_deepdata; + } + else + { + if (doread) imgdata.resize (bytes); + } rv = exr_decoding_choose_default_routines (f, part, &decoder); - if (rv != EXR_ERR_SUCCESS) break; + if (rv != EXR_ERR_SUCCESS) + { + frv = rv; + break; + } } else { rv = exr_decoding_update (f, part, &cinfo, &decoder); if (rv != EXR_ERR_SUCCESS) { + frv = rv; if (reduceTime) break; continue; } @@ -1273,20 +1368,25 @@ readCoreScanlinePart ( if (doread) { - uint8_t* dptr = &(imgdata[0]); - for (int c = 0; c < decoder.channel_count; c++) + if (cinfo.type != EXR_STORAGE_DEEP_SCANLINE) { - exr_coding_channel_info_t& outc = decoder.channels[c]; - outc.decode_to_ptr = dptr; - outc.user_pixel_stride = outc.user_bytes_per_element; - outc.user_line_stride = outc.user_pixel_stride * width; - dptr += width * (uint64_t) outc.user_bytes_per_element * + uint8_t* dptr = &(imgdata[0]); + for (int c = 0; c < decoder.channel_count; c++) + { + exr_coding_channel_info_t& outc = decoder.channels[c]; + outc.decode_to_ptr = dptr; + outc.user_pixel_stride = outc.user_bytes_per_element; + outc.user_line_stride = outc.user_pixel_stride * width; + + dptr += width * (uint64_t) outc.user_bytes_per_element * (uint64_t) lines_per_chunk; + } } rv = exr_decoding_run (f, part, &decoder); if (rv != EXR_ERR_SUCCESS) { + frv = rv; if (reduceTime) break; } } @@ -1294,7 +1394,7 @@ readCoreScanlinePart ( exr_decoding_destroy (f, &decoder); - return (rv != EXR_ERR_SUCCESS); + return (frv != EXR_ERR_SUCCESS); } //////////////////////////////////////// @@ -1303,7 +1403,7 @@ bool readCoreTiledPart ( exr_context_t f, int part, bool reduceMemory, bool reduceTime) { - exr_result_t rv; + exr_result_t rv, frv; exr_attr_box2i_t datawin; rv = exr_get_data_window (f, part, &datawin); @@ -1321,6 +1421,7 @@ readCoreTiledPart ( rv = exr_get_tile_levels (f, part, &levelsx, &levelsy); if (rv != EXR_ERR_SUCCESS) return true; + frv = rv; bool keepgoing = true; for (int32_t ylevel = 0; keepgoing && ylevel < levelsy; ++ylevel) { @@ -1330,6 +1431,7 @@ readCoreTiledPart ( rv = exr_get_level_sizes (f, part, xlevel, ylevel, &levw, &levh); if (rv != EXR_ERR_SUCCESS) { + frv = rv; if (reduceTime) { keepgoing = false; @@ -1342,6 +1444,7 @@ readCoreTiledPart ( rv = exr_get_tile_sizes (f, part, xlevel, ylevel, &curtw, &curth); if (rv != EXR_ERR_SUCCESS) { + frv = rv; if (reduceTime) { keepgoing = false; @@ -1371,6 +1474,7 @@ readCoreTiledPart ( f, part, tx, ty, xlevel, ylevel, &cinfo); if (rv != EXR_ERR_SUCCESS) { + frv = rv; if (reduceTime) { keepgoing = false; @@ -1385,6 +1489,7 @@ readCoreTiledPart ( exr_decoding_initialize (f, part, &cinfo, &decoder); if (rv != EXR_ERR_SUCCESS) { + frv = rv; keepgoing = false; break; } @@ -1409,11 +1514,20 @@ readCoreTiledPart ( if (reduceMemory && bytes >= gMaxTileBytes) doread = false; - if (doread) tiledata.resize (bytes); + if (cinfo.type == EXR_STORAGE_DEEP_TILED) + { + decoder.decoding_user_data = &tiledata; + decoder.realloc_nonimage_data_fn = &realloc_deepdata; + } + else + { + if (doread) tiledata.resize (bytes); + } rv = exr_decoding_choose_default_routines ( f, part, &decoder); if (rv != EXR_ERR_SUCCESS) { + frv = rv; keepgoing = false; break; } @@ -1423,6 +1537,7 @@ readCoreTiledPart ( rv = exr_decoding_update (f, part, &cinfo, &decoder); if (rv != EXR_ERR_SUCCESS) { + frv = rv; if (reduceTime) { keepgoing = false; @@ -1434,24 +1549,28 @@ readCoreTiledPart ( if (doread) { - uint8_t* dptr = &(tiledata[0]); - for (int c = 0; c < decoder.channel_count; c++) + if (cinfo.type != EXR_STORAGE_DEEP_TILED) { - exr_coding_channel_info_t& outc = - decoder.channels[c]; - outc.decode_to_ptr = dptr; - outc.user_pixel_stride = - outc.user_bytes_per_element; - outc.user_line_stride = - outc.user_pixel_stride * curtw; - dptr += (uint64_t) curtw * + uint8_t* dptr = &(tiledata[0]); + for (int c = 0; c < decoder.channel_count; c++) + { + exr_coding_channel_info_t& outc = + decoder.channels[c]; + outc.decode_to_ptr = dptr; + outc.user_pixel_stride = + outc.user_bytes_per_element; + outc.user_line_stride = + outc.user_pixel_stride * curtw; + dptr += (uint64_t) curtw * (uint64_t) outc.user_bytes_per_element * (uint64_t) curth; + } } rv = exr_decoding_run (f, part, &decoder); if (rv != EXR_ERR_SUCCESS) { + frv = rv; if (reduceTime) { keepgoing = false; @@ -1486,17 +1605,14 @@ checkCoreFile (exr_context_t f, bool reduceMemory, bool reduceTime) rv = exr_get_storage (f, p, &store); if (rv != EXR_ERR_SUCCESS) return true; - // TODO: Need to fill this in - if (store == EXR_STORAGE_DEEP_SCANLINE || - store == EXR_STORAGE_DEEP_TILED) - continue; - - if (store == EXR_STORAGE_SCANLINE) + if (store == EXR_STORAGE_SCANLINE || + store == EXR_STORAGE_DEEP_SCANLINE) { if (readCoreScanlinePart (f, p, reduceMemory, reduceTime)) return true; } - else if (store == EXR_STORAGE_TILED) + else if (store == EXR_STORAGE_TILED || + store == EXR_STORAGE_DEEP_TILED) { if (readCoreTiledPart (f, p, reduceMemory, reduceTime)) return true; } @@ -1535,6 +1651,20 @@ runCoreChecks (const char* filename, bool reduceMemory, bool reduceTime) cinit.error_handler_fn = &core_error_handler_cb; + if (reduceMemory || reduceTime) + { + /* could use set_default functions for this, but those just + * initialize the context, doing it in the initializer is mt + * safe... + * exr_set_default_maximum_image_size (2048, 2048); + * exr_set_default_maximum_tile_size (512, 512); + */ + cinit.max_image_width = 2048; + cinit.max_image_height = 2048; + cinit.max_tile_width = 512; + cinit.max_tile_height = 512; + } + rv = exr_start_read (&f, filename, &cinit); if (rv != EXR_ERR_SUCCESS) return true; @@ -1604,6 +1734,19 @@ runCoreChecks ( cinit.read_fn = &memstream_read; cinit.size_fn = &memstream_size; cinit.error_handler_fn = &core_error_handler_cb; + if (reduceMemory || reduceTime) + { + /* could use set_default functions for this, but those just + * initialize the context, doing it in the initializer is mt + * safe... + * exr_set_default_maximum_image_size (2048, 2048); + * exr_set_default_maximum_tile_size (512, 512); + */ + cinit.max_image_width = 2048; + cinit.max_image_height = 2048; + cinit.max_tile_width = 512; + cinit.max_tile_height = 512; + } rv = exr_start_read (&f, "", &cinit); if (rv != EXR_ERR_SUCCESS) return true; diff --git a/src/test/CMakeLists.txt b/src/test/CMakeLists.txt index 44d9185d8b..25f429daa2 100644 --- a/src/test/CMakeLists.txt +++ b/src/test/CMakeLists.txt @@ -4,10 +4,15 @@ # We require this to get object library link library support and # combined python 2 + 3 support -add_subdirectory(IexTest) -add_subdirectory(OpenEXRCoreTest) -add_subdirectory(OpenEXRTest) -add_subdirectory(OpenEXRUtilTest) -add_subdirectory(OpenEXRFuzzTest) -add_subdirectory(bin) +if (OPENEXR_TEST_LIBRARIES) + add_subdirectory(IexTest) + add_subdirectory(OpenEXRCoreTest) + add_subdirectory(OpenEXRTest) + add_subdirectory(OpenEXRUtilTest) + add_subdirectory(OpenEXRFuzzTest) +endif() + +if (OPENEXR_BUILD_TOOLS AND OPENEXR_TEST_TOOLS) + add_subdirectory(bin) +endif() diff --git a/src/test/OpenEXRTest/CMakeLists.txt b/src/test/OpenEXRTest/CMakeLists.txt index f91e6160fb..0ae6606998 100644 --- a/src/test/OpenEXRTest/CMakeLists.txt +++ b/src/test/OpenEXRTest/CMakeLists.txt @@ -25,6 +25,8 @@ add_executable(OpenEXRTest testChannels.h testCompositeDeepScanLine.cpp testCompositeDeepScanLine.h + testCompressionApi.cpp + testCompressionApi.h testCompression.cpp testCompression.h testConversion.cpp @@ -156,6 +158,7 @@ define_openexr_tests( testBadTypeAttributes testChannels testCompositeDeepScanLine + testCompressionApi testCompression testConversion testCopyDeepScanLine diff --git a/src/test/OpenEXRTest/bswap_32.h b/src/test/OpenEXRTest/bswap_32.h index f67cc036a1..33c636b9c7 100644 --- a/src/test/OpenEXRTest/bswap_32.h +++ b/src/test/OpenEXRTest/bswap_32.h @@ -19,8 +19,8 @@ # include # define bswap_32(x) swap32 (x) #elif defined(__NetBSD__) -# include # include +# include # define bswap_32(x) bswap32 (x) #else # include diff --git a/src/test/OpenEXRTest/main.cpp b/src/test/OpenEXRTest/main.cpp index 942c4b2db4..cc1ca2a4ce 100644 --- a/src/test/OpenEXRTest/main.cpp +++ b/src/test/OpenEXRTest/main.cpp @@ -17,6 +17,7 @@ #include "testChannels.h" #include "testCompositeDeepScanLine.h" #include "testCompression.h" +#include "testCompressionApi.h" #include "testConversion.h" #include "testCopyDeepScanLine.h" #include "testCopyDeepTiled.h" @@ -189,6 +190,7 @@ main (int argc, char* argv[]) TEST (testAttributes, "core"); TEST (testCustomAttributes, "core"); TEST (testLineOrder, "basic"); + TEST (testCompressionApi, "basic"); TEST (testCompression, "basic"); TEST (testCopyPixels, "basic"); TEST (testLut, "basic"); diff --git a/src/test/OpenEXRTest/testCompressionApi.cpp b/src/test/OpenEXRTest/testCompressionApi.cpp new file mode 100644 index 0000000000..53b6ecd265 --- /dev/null +++ b/src/test/OpenEXRTest/testCompressionApi.cpp @@ -0,0 +1,95 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) Contributors to the OpenEXR Project. +// + +// unset NDEBUG to make assert() operational in release mode. +#ifdef NDEBUG +# undef NDEBUG +#endif + +#include "ImfCompression.h" + +#include +#include + +using namespace OPENEXR_IMF_NAMESPACE; +using namespace std; + +// To run this test only: +// > cd build +// > ctest -R testCompressionApi + +void +testCompressionApi (const string& tempDir) +{ + try + { + cout << "Testing compression API functions." << endl; + + // update this if you add a new compressor. + string codecList = "none/rle/zips/zip/piz/pxr24/b44/b44a/dwaa/dwab"; + + int numMethods = static_cast (NUM_COMPRESSION_METHODS); + // update this if you add a new compressor. + assert (numMethods == 10); + + for (int i = 0; i < numMethods; i++) + { + assert (isValidCompression (i) == true); + + Compression c = static_cast (i); + Compression id; + string name, desc; + + getCompressionNameFromId (c, name); + assert (codecList.find (name) != string::npos); + + getCompressionIdFromName (name, id); + assert (id >= NO_COMPRESSION && id < NUM_COMPRESSION_METHODS); + assert (id == c); + + getCompressionDescriptionFromId (c, desc); + assert (!desc.empty ()); + + assert (isValidCompression (id) == true); + + assert (getCompressionNumScanlines (c) > 0); + + // update this if you add a new lossy compressor. + switch (c) + { + case NO_COMPRESSION: + case RLE_COMPRESSION: + case ZIPS_COMPRESSION: + case ZIP_COMPRESSION: + case PIZ_COMPRESSION: + assert (isLossyCompression (c) == false); + break; + + default: assert (isLossyCompression (c) == true); break; + } + + // update this if you add a new deep compressor. + switch (c) + { + case NO_COMPRESSION: + case RLE_COMPRESSION: + case ZIPS_COMPRESSION: + assert (isValidDeepCompression (c) == true); + break; + + default: assert (isValidDeepCompression (c) == false); break; + } + } + + string codecs; + getCompressionNamesString ("/", codecs); + assert (codecs == codecList); + } + catch (const exception& e) + { + cerr << "ERROR -- caught exception: " << e.what () << endl; + assert (false); + } +} diff --git a/src/test/OpenEXRTest/testCompressionApi.h b/src/test/OpenEXRTest/testCompressionApi.h new file mode 100644 index 0000000000..1677bce2cf --- /dev/null +++ b/src/test/OpenEXRTest/testCompressionApi.h @@ -0,0 +1,8 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) Contributors to the OpenEXR Project. +// + +#include + +void testCompressionApi (const std::string& tempDir); diff --git a/src/test/bin/CMakeLists.txt b/src/test/bin/CMakeLists.txt index e53b87900f..32a204ad38 100644 --- a/src/test/bin/CMakeLists.txt +++ b/src/test/bin/CMakeLists.txt @@ -63,6 +63,7 @@ if(BUILD_TESTING) exrenvmap exrmakepreview exrmaketiled + exrmanifest exrmultiview exrmultipart exrstdattr diff --git a/src/test/bin/test_exrmanifest.py b/src/test/bin/test_exrmanifest.py new file mode 100644 index 0000000000..df85bf7c00 --- /dev/null +++ b/src/test/bin/test_exrmanifest.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python + +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) Contributors to the OpenEXR Project. + +import sys, os, tempfile, atexit +from subprocess import PIPE, run + +print(f"testing exrmanifest: {sys.argv}") + +src_dir = os.path.dirname (sys.argv[0]) +exrmanifest = sys.argv[1] +exrinfo = sys.argv[2] +image_dir = sys.argv[3] +version = sys.argv[4] + +# no args = usage message, error +result = run ([exrmanifest], stdout=PIPE, stderr=PIPE, universal_newlines=True) +print(" ".join(result.args)) +assert(result.returncode != 0), "\n"+result.stderr +assert(result.stderr.startswith ("Usage: ")), "\n"+result.stderr + +# -h = usage message +result = run ([exrmanifest, "-h"], stdout=PIPE, stderr=PIPE, universal_newlines=True) +print(" ".join(result.args)) +assert(result.returncode == 0), "\n"+result.stderr +assert(result.stdout.startswith ("Usage: ")), "\n"+result.stdout + +result = run ([exrmanifest, "--help"], stdout=PIPE, stderr=PIPE, universal_newlines=True) +print(" ".join(result.args)) +assert(result.returncode == 0), "\n"+result.stderr +assert(result.stdout.startswith ("Usage: ")), "\n"+result.stdout + +# --version +result = run ([exrmanifest, "--version"], stdout=PIPE, stderr=PIPE, universal_newlines=True) +print(" ".join(result.args)) +assert(result.returncode == 0), "\n"+result.stderr +assert(result.stdout.startswith ("exrmanifest")), "\n"+result.stdout +assert(version in result.stdout), "\n"+result.stdout + +# invalid arguments +result = run ([exrmanifest, "foo.exr", "bar.exr"], stdout=PIPE, stderr=PIPE, universal_newlines=True) +print(" ".join(result.args)) +assert(result.returncode != 0), "\n"+result.stderr + +for test_image in ["11.deep.exr", "42.deep.exr", "64.deep.exr", "multivariate.deep.exr", "objectid.deep.exr"]: + test_file = src_dir + "/test_images/" + test_image + result = run ([exrmanifest, test_file], stdout=PIPE, stderr=PIPE, universal_newlines=True) + print(" ".join(result.args)) + assert(result.returncode == 0), "\n"+result.stderr + stdout_is = result.stdout + with open (test_file + ".txt", 'r') as file: + stdout_should_be = file.read() + assert stdout_is == stdout_should_be + +print("success") + + + + + + + + + diff --git a/src/test/bin/test_images/11.deep.exr b/src/test/bin/test_images/11.deep.exr new file mode 100644 index 0000000000..15f1a47418 Binary files /dev/null and b/src/test/bin/test_images/11.deep.exr differ diff --git a/src/test/bin/test_images/11.deep.exr.txt b/src/test/bin/test_images/11.deep.exr.txt new file mode 100644 index 0000000000..36972267a6 --- /dev/null +++ b/src/test/bin/test_images/11.deep.exr.txt @@ -0,0 +1,26 @@ + channels : id + hashScheme: MurmurHash3_32 + encoding : id + lifetime : stable + model material + 523461226 circle/medium blue + 683859976 circle/small blue + 1552124883 blob/medium yellow + 2202049807 blob/small green + 2303547305 blob/small blue + 2433329986 circle/big yellow + 2698909330 blob/small yellow + 3182339638 circle/small green + 3236172513 blob/big white + 3626859046 blob/big yellow + 3739501011 circle/small yellow + + + channels : particleid + hashScheme: none + encoding : id + lifetime : shot + +raw text size : 332 +uncompressed size: 262 +compressed size : 233 diff --git a/src/test/bin/test_images/42.deep.exr b/src/test/bin/test_images/42.deep.exr new file mode 100644 index 0000000000..29bea765e5 Binary files /dev/null and b/src/test/bin/test_images/42.deep.exr differ diff --git a/src/test/bin/test_images/42.deep.exr.txt b/src/test/bin/test_images/42.deep.exr.txt new file mode 100644 index 0000000000..0658a26f49 --- /dev/null +++ b/src/test/bin/test_images/42.deep.exr.txt @@ -0,0 +1,35 @@ + channels : modelid + hashScheme: MurmurHash3_32 + encoding : id + lifetime : stable + model + 147148503 circle/medium + 1252544961 blob/medium + 1917926828 circle/big + 3170049167 circle/small + 3338960663 blob/small + 3411631724 blob/big + + + channels : materialid + hashScheme: MurmurHash3_32 + encoding : id + lifetime : stable + material + 489905694 green + 507819813 yellow + 2340321182 cyan + 3683581971 white + 3905155331 blue + 3947898621 red + 4179825792 magenta + + + channels : particleid + hashScheme: none + encoding : id + lifetime : shot + +raw text size : 283 +uncompressed size: 334 +compressed size : 261 diff --git a/src/test/bin/test_images/64.deep.exr b/src/test/bin/test_images/64.deep.exr new file mode 100644 index 0000000000..be22120f82 Binary files /dev/null and b/src/test/bin/test_images/64.deep.exr differ diff --git a/src/test/bin/test_images/64.deep.exr.txt b/src/test/bin/test_images/64.deep.exr.txt new file mode 100644 index 0000000000..a59638286e --- /dev/null +++ b/src/test/bin/test_images/64.deep.exr.txt @@ -0,0 +1,35 @@ + channels : model.id0,model.id1 + hashScheme: MurmurHash3_64 + encoding : id2 + lifetime : stable + model + 97515517976221909 circle/medium + 742872203054891411 circle/small + 6191538508622224182 blob/medium + 6650017402852611196 blob/big + 9225128210671310825 circle/big + 17511330964567540308 blob/small + + + channels : material.id0,material.id1 + hashScheme: MurmurHash3_64 + encoding : id2 + lifetime : stable + material + 2470908766622966990 blue + 2785030139762120788 white + 7547477443120661518 yellow + 14272065673169316387 red + 14592330334135882751 green + 16918673263070507131 magenta + 18125959695279297844 cyan + + + channels : particleid + hashScheme: none + encoding : id + lifetime : shot + +raw text size : 432 +uncompressed size: 415 +compressed size : 333 diff --git a/src/test/bin/test_images/multivariate.deep.exr b/src/test/bin/test_images/multivariate.deep.exr new file mode 100644 index 0000000000..e6b4b81cd2 Binary files /dev/null and b/src/test/bin/test_images/multivariate.deep.exr differ diff --git a/src/test/bin/test_images/multivariate.deep.exr.txt b/src/test/bin/test_images/multivariate.deep.exr.txt new file mode 100644 index 0000000000..e0d459fdec --- /dev/null +++ b/src/test/bin/test_images/multivariate.deep.exr.txt @@ -0,0 +1,54 @@ + channels : id + hashScheme: MurmurHash3_32 + encoding : id + lifetime : stable + model material + 127035619 circle/medium red + 147085119 blob/big cyan + 319332210 blob/medium white + 409096490 circle/small magenta + 523461226 circle/medium blue + 678492822 circle/big blue + 683859976 circle/small blue + 1058109025 circle/small cyan + 1193275200 blob/medium blue + 1503246798 blob/small white + 1552124883 blob/medium yellow + 1613501244 blob/small cyan + 1672208113 blob/medium green + 1687665471 blob/big green + 1741895317 circle/medium yellow + 1841370478 blob/small magenta + 1863854195 circle/small red + 1865183722 circle/big cyan + 2056745377 blob/big magenta + 2202049807 blob/small green + 2303547305 blob/small blue + 2369798380 circle/medium magenta + 2433329986 circle/big yellow + 2439705590 circle/medium cyan + 2505186565 circle/small white + 2698909330 blob/small yellow + 2862333315 blob/medium magenta + 2898564507 circle/big magenta + 2979371527 blob/big blue + 3007860936 circle/big red + 3182339638 circle/small green + 3236172513 blob/big white + 3497437340 blob/medium red + 3626859046 blob/big yellow + 3656820528 circle/medium white + 3693645783 blob/big red + 3739501011 circle/small yellow + 3936563811 circle/big green + 4048943790 circle/medium green + + + channels : particleid + hashScheme: none + encoding : id + lifetime : shot + +raw text size : 1099 +uncompressed size: 449 +compressed size : 416 diff --git a/src/test/bin/test_images/objectid.deep.exr b/src/test/bin/test_images/objectid.deep.exr new file mode 100644 index 0000000000..2964344ea7 Binary files /dev/null and b/src/test/bin/test_images/objectid.deep.exr differ diff --git a/src/test/bin/test_images/objectid.deep.exr.txt b/src/test/bin/test_images/objectid.deep.exr.txt new file mode 100644 index 0000000000..e9b0b3a465 --- /dev/null +++ b/src/test/bin/test_images/objectid.deep.exr.txt @@ -0,0 +1 @@ +no manifest found diff --git a/src/wrappers/python/CMakeLists.txt b/src/wrappers/python/CMakeLists.txt index a136ad5531..b240eb04a5 100644 --- a/src/wrappers/python/CMakeLists.txt +++ b/src/wrappers/python/CMakeLists.txt @@ -1,23 +1,42 @@ # SPDX-License-Identifier: BSD-3-Clause # Copyright Contributors to the OpenEXR Project. +message(STATUS "Building OpenEXR python bindings") + if(NOT "${CMAKE_PROJECT_NAME}" STREQUAL "OpenEXR") cmake_minimum_required(VERSION 3.12) project(PyOpenEXR) find_package(OpenEXR) endif() -add_library (PyOpenEXR SHARED OpenEXR.cpp) +find_package(Python COMPONENTS Interpreter Development.Module REQUIRED) + +python_add_library (PyOpenEXR MODULE OpenEXR.cpp) -set (Python_ADDITIONAL_VERSIONS 3) -find_package (PythonLibs REQUIRED) -find_package (PythonInterp REQUIRED) +target_link_libraries (PyOpenEXR PRIVATE "${Python_LIBRARIES}" OpenEXR::OpenEXR) -include_directories ("${PYTHON_INCLUDE_DIRS}") +# The python module should be called "OpenEXR.so", not "PyOpenEXR.so", +# but "OpenEXR" is taken as a library name by the main lib, so specify +# the name explicitly here. -set_target_properties (PyOpenEXR PROPERTIES PREFIX "") set_target_properties (PyOpenEXR PROPERTIES OUTPUT_NAME "OpenEXR") -set_target_properties (PyOpenEXR PROPERTIES SUFFIX ".so") -target_link_libraries (PyOpenEXR "${PYTHON_LIBRARIES}" OpenEXR::OpenEXR) +configure_file(Imath.py ${CMAKE_CURRENT_BINARY_DIR}/Imath.py COPYONLY) + +set(PYTHON_INSTALL_DIR "python/OpenEXR") +if(SKBUILD) + set(PYTHON_INSTALL_DIR ${SKBUILD_PLATLIB_DIR}) +endif() + +install(TARGETS PyOpenEXR DESTINATION ${PYTHON_INSTALL_DIR} COMPONENT python) +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/Imath.py DESTINATION ${PYTHON_INSTALL_DIR} COMPONENT python) +if(BUILD_TESTING AND OPENEXR_TEST_PYTHON) + + add_test(OpenEXR.PyOpenEXR pytest ${CMAKE_CURRENT_SOURCE_DIR}/tests) + + set_tests_properties(OpenEXR.PyOpenEXR PROPERTIES + ENVIRONMENT "PYTHONPATH=${CMAKE_CURRENT_BINARY_DIR}" + ) + +endif() diff --git a/src/wrappers/python/README.md b/src/wrappers/python/README.md new file mode 100644 index 0000000000..d7c15c989a --- /dev/null +++ b/src/wrappers/python/README.md @@ -0,0 +1,142 @@ + + + +[![License](https://img.shields.io/github/license/AcademySoftwareFoundation/openexr)](https://github.com/AcademySoftwareFoundation/openexr/blob/main/LICENSE.md) +[![CII Best Practices](https://bestpractices.coreinfrastructure.org/projects/2799/badge)](https://bestpractices.coreinfrastructure.org/projects/2799) +[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/AcademySoftwareFoundation/openexr/badge)](https://securityscorecards.dev/viewer/?uri=github.com/AcademySoftwareFoundation/openexr) +[![Build Status](https://github.com/AcademySoftwareFoundation/openexr/workflows/CI/badge.svg)](https://github.com/AcademySoftwareFoundation/openexr/actions?query=workflow%3ACI) +[![Analysis Status](https://github.com/AcademySoftwareFoundation/openexr/workflows/Analysis/badge.svg)](https://github.com/AcademySoftwareFoundation/openexr/actions?query=workflow%3AAnalysis) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=AcademySoftwareFoundation_openexr&metric=alert_status)](https://sonarcloud.io/dashboard?id=AcademySoftwareFoundation_openexr) + +# OpenEXR + +OpenEXR provides the specification and reference implementation of the +EXR file format, the professional-grade image storage format of the +motion picture industry. + +The purpose of EXR format is to accurately and efficiently represent +high-dynamic-range scene-linear image data and associated metadata, +with strong support for multi-part, multi-channel use cases. + +OpenEXR is widely used in host application software where accuracy is +critical, such as photorealistic rendering, texture access, image +compositing, deep compositing, and DI. + +## OpenEXR Project Mission + +The goal of the OpenEXR project is to keep the EXR format reliable and +modern and to maintain its place as the preferred image format for +entertainment content creation. + +Major revisions are infrequent, and new features will be carefully +weighed against increased complexity. The principal priorities of the +project are: + +* Robustness, reliability, security +* Backwards compatibility, data longevity +* Performance - read/write/compression/decompression time +* Simplicity, ease of use, maintainability +* Wide adoption, multi-platform support - Linux, Windows, macOS, and others + +OpenEXR is intended solely for 2D data. It is not appropriate for +storage of volumetric data, cached or lit 3D scenes, or more complex +3D data such as light fields. + +The goals of the Imath project are simplicity, ease of use, +correctness and verifiability, and breadth of adoption. Imath is not +intended to be a comprehensive linear algebra or numerical analysis +package. + +## Python Module + +The OpenEXR python module provides rudimentary support for reading and +writing basic scanline image data. Many features of the file format +are not yet supported, including: + +- Writing of tiled images +- Multiresoltion images +- Deep image data +- Some attribute types +- Nonunity channel sampling frequencies +- No support for interleaved channel data + +## Project Governance + +OpenEXR is a project of the [Academy Software +Foundation](https://www.aswf.io). See the project's [governance +policies](https://github.com/AcademySoftwareFoundation/openexr/blob/main/GOVERNANCE.md), [contribution guidelines](https://github.com/AcademySoftwareFoundation/openexr/blob/main/CONTRIBUTING.md), and [code of conduct](https://github.com/AcademySoftwareFoundation/openexr/blob/main/CODE_OF_CONDUCT.md) +for more information. + +# Quick Start + +The "hello, world" image writer: + + import OpenEXR + + width = 10 + height = 10 + size = width * height + + h = OpenEXR.Header(width,height) + h['channels'] = {'R' : Imath.Channel(FLOAT), + 'G' : Imath.Channel(FLOAT), + 'B' : Imath.Channel(FLOAT), + 'A' : Imath.Channel(FLOAT)} + o = OpenEXR.OutputFile("hello.exr", h) + r = array('f', [n for n in range(size*0,size*1)]).tobytes() + g = array('f', [n for n in range(size*1,size*2)]).tobytes() + b = array('f', [n for n in range(size*2,size*3)]).tobytes() + a = array('f', [n for n in range(size*3,size*4)]).tobytes() + channels = {'R' : r, 'G' : g, 'B' : b, 'A' : a} + o.writePixels(channels) + o.close() + +# Community + +* **Ask a question:** + + - Email: openexr-dev@lists.aswf.io + + - Slack: [academysoftwarefdn#openexr](https://academysoftwarefdn.slack.com/archives/CMLRW4N73) + +* **Attend a meeting:** + + - Technical Steering Committee meetings are open to the + public, fortnightly on Thursdays, 1:30pm Pacific Time. + + - Calendar: https://lists.aswf.io/g/openexr-dev/calendar + + - Meeting notes: https://wiki.aswf.io/display/OEXR/TSC+Meetings + +* **Report a bug:** + + - Submit an Issue: https://github.com/AcademySoftwareFoundation/openexr/issues + +* **Report a security vulnerability:** + + - Email to security@openexr.com + +* **Contribute a Fix, Feature, or Improvement:** + + - Read the [Contribution Guidelines](https://github.com/AcademySoftwareFoundation/openexr/blob/main/CONTRIBUTING.md) and [Code of Conduct](https://github.com/AcademySoftwareFoundation/openexr/blob/main/CODE_OF_CONDUCT.md) + + - Sign the [Contributor License + Agreement](https://contributor.easycla.lfx.linuxfoundation.org/#/cla/project/2e8710cb-e379-4116-a9ba-964f83618cc5/user/564e571e-12d7-4857-abd4-898939accdd7) + + - Submit a Pull Request: https://github.com/AcademySoftwareFoundation/openexr/pulls + +# Resources + +- Website: http://www.openexr.com +- Technical documentation: https://openexr.readthedocs.io +- Porting help: [OpenEXR/Imath Version 2.x to 3.x Porting Guide](https://openexr.readthedocs.io/en/latest/PortingGuide.html) +- Reference images: https://github.com/AcademySoftwareFoundation/openexr-images +- Security policy: [SECURITY.md](https://github.com/AcademySoftwareFoundation/openexr/blob/main/SECURITY.md) +- Release notes: [CHANGES.md](https://github.com/AcademySoftwareFoundation/openexr/blob/main/CHANGES.md) +- Contributors: [CONTRIBUTORS.md](https://github.com/AcademySoftwareFoundation/openexr/blob/main/CONTRIBUTORS.md) + +# License + +OpenEXR is licensed under the [BSD-3-Clause license](https://github.com/AcademySoftwareFoundation/openexr/blob/main/LICENSE.md). + + diff --git a/src/wrappers/python/libdeflate.patch b/src/wrappers/python/libdeflate.patch deleted file mode 100644 index a1e2e5b0cc..0000000000 --- a/src/wrappers/python/libdeflate.patch +++ /dev/null @@ -1,12 +0,0 @@ ---- CMakeLists.txt 2023-07-14 08:51:51.375958419 +0200 -+++ CMakeLists.txt 2023-07-14 08:47:47.854104430 +0200 -@@ -183,7 +183,8 @@ - endif() - set_target_properties(libdeflate_static PROPERTIES - OUTPUT_NAME ${STATIC_LIB_NAME} -- PUBLIC_HEADER libdeflate.h) -+ PUBLIC_HEADER libdeflate.h -+ POSITION_INDEPENDENT_CODE ON) - target_include_directories(libdeflate_static PUBLIC ${LIB_INCLUDE_DIRS}) - target_compile_definitions(libdeflate_static PRIVATE ${LIB_COMPILE_DEFINITIONS}) - target_compile_options(libdeflate_static PRIVATE ${LIB_COMPILE_OPTIONS}) diff --git a/src/wrappers/python/openexr_skbuild_plugin.py b/src/wrappers/python/openexr_skbuild_plugin.py new file mode 100644 index 0000000000..ea09221b46 --- /dev/null +++ b/src/wrappers/python/openexr_skbuild_plugin.py @@ -0,0 +1,106 @@ +# Copyright Contributors to the MaterialX Project +# SPDX-License-Identifier: Apache-2.0 +# copied from: https://github.com/AcademySoftwareFoundation/MaterialX/blob/main/python/mtx_skbuild_plugin.py +# Modifications Copyright (c) Contributors to the OpenEXR Project. + +""" +This is a custom scikit-build-core plugin that will +fetch the OpenEXR version from the CMake project. +""" +import os +import tempfile +import subprocess +from pathlib import Path +from typing import FrozenSet, Dict, Optional, Union, List + +from scikit_build_core.file_api.query import stateless_query +from scikit_build_core.file_api.reply import load_reply_dir + + +def dynamic_metadata( + fields: FrozenSet[str], + settings: Optional[Dict[str, object]] = None, +) -> Dict[str, Union[str, Dict[str, Optional[str]]]]: + print("openexr_skbuild_plugin: Computing OpenEXR version from CMake...") + + if fields != "version": + msg = f"Only the 'version' field is supported: fields={fields}" + raise ValueError(msg) + + if settings: + msg = "No inline configuration is supported" + raise ValueError(msg) + + if "OPENEXR_RELEASE_CANDIDATE_TAG" in os.environ: + + # e.g. "v3.1.2-rc4" + # + # If OPENEXR_RELEASE_CANDIDATE_TAG is set, + # the build is for a publish to test.pypi.org. Multiple test + # publishes may happen in the course of preparing for a + # release, but published packages require unique + # names/versions, so use the release candidate tag as the + # version (minus the leading 'v'), + + rct = os.environ["OPENEXR_RELEASE_CANDIDATE_TAG"] + version = rct[1:] + + else: + + current_dir = os.path.dirname(__file__) + + with tempfile.TemporaryDirectory() as tmpdir: + # We will use CMake's file API to get the version + # instead of parsing the CMakeLists files. + + # First generate the query folder so that CMake can generate replies. + reply_dir = stateless_query(Path(tmpdir)) + + # Run cmake (configure). CMake will generate a reply automatically. + try: + subprocess.run( + [ + "cmake", + "-S", + current_dir + "../../../..", + "-B", + tmpdir, + "-DOPENEXR_BUILD_LIBS=OFF", + ], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + check=True, + text=True, + ) + except subprocess.CalledProcessError as exc: + print(exc.stdout) + raise RuntimeError( + "Failed to configure project to get the version" + ) from exc + + # Get the generated replies. + index = load_reply_dir(reply_dir) + + # Get the version from the CMAKE_PROJECT_VERSION variable. + entries = [ + entry + for entry in index.reply.cache_v2.entries + if entry.name == "CMAKE_PROJECT_VERSION" + ] + + if not entries: + raise ValueError("Could not find OpenEXR version from CMake project") + + if len(entries) > 1: + raise ValueError("More than one entry for CMAKE_PROJECT_VERSION found...") + + version = entries[0].value + + print("openexr_skbuild_plugin: Computed version: {0}".format(version)) + + return version + +def get_requires_for_dynamic_metadata( + _settings: Optional[Dict[str, object]] = None, +) -> List[str]: + return ["cmake"] diff --git a/src/wrappers/python/setup.py b/src/wrappers/python/setup.py deleted file mode 100644 index 5cf4cda608..0000000000 --- a/src/wrappers/python/setup.py +++ /dev/null @@ -1,83 +0,0 @@ -from setuptools import setup, Extension -import os -import platform -import re - - -DESC = """Python bindings for the OpenEXR image file format. - -This is a script to autobuild the wheels using github actions. Please, do not -use it manually - -If you detect any problem, please feel free to report the issue on the GitHub -page: - -https://github.com/AcademySoftwareFoundation/openexr/issues -""" - -# Get the version and library suffix for both OpenEXR and Imath from -# the .pc pkg-config file. - -def pkg_config(var, pkg): - with open(f'./openexr.install/lib/pkgconfig/{pkg}.pc', 'r') as f: - return re.search(f'{var}([^ \n]+)', f.read()).group(1) - -imath_libsuffix = pkg_config("libsuffix=", "Imath") -openexr_libsuffix = pkg_config("libsuffix=", "OpenEXR") -openexr_version = pkg_config("Version: ", "OpenEXR") -openexr_version_major, openexr_version_minor, openexr_version_patch = openexr_version.split('.') - -libs=[] -libs_static=[f'OpenEXR{openexr_libsuffix}', - f'IlmThread{openexr_libsuffix}', - f'Iex{openexr_libsuffix}', - f'Imath{imath_libsuffix}', - f'OpenEXRCore{openexr_libsuffix}', - ] -definitions = [('PYOPENEXR_VERSION_MAJOR', f'{openexr_version_major}'), - ('PYOPENEXR_VERSION_MINOR', f'{openexr_version_minor}'), - ('PYOPENEXR_VERSION_PATCH', f'{openexr_version_patch}'),] -if platform.system() == "Windows": - definitions = [('PYOPENEXR_VERSION', f'\\"{openexr_version}\\"')] -extra_compile_args = [] -if platform.system() == 'Darwin': - extra_compile_args += ['-std=c++11', - '-Wc++11-extensions', - '-Wc++11-long-long'] - -libs_dir = "./openexr.install/lib/" -if not os.path.isdir(libs_dir): - libs_dir = "./openexr.install/lib64/" -if platform.system() == "Windows": - extra_link_args = [libs_dir + lib + ".lib" - for lib in libs_static] - extra_link_args = extra_link_args + [ - "ws2_32.lib", "dbghelp.lib", "psapi.lib", "kernel32.lib", "user32.lib", - "gdi32.lib", "winspool.lib", "shell32.lib", "ole32.lib", - "oleaut32.lib", "uuid.lib", "comdlg32.lib", "advapi32.lib"] -else: - extra_link_args = [libs_dir + "lib" + lib + ".a" - for lib in libs_static] - - -setup(name='OpenEXR', - author = 'Contributors to the OpenEXR Project', - author_email = 'info@openexr.com', - url = 'https://github.com/AcademySoftwareFoundation/openexr', - description = "Python bindings for the OpenEXR image file format", - long_description = DESC, - version=openexr_version, - ext_modules=[ - Extension('OpenEXR', - ['OpenEXR.cpp'], - language='c++', - define_macros=definitions, - include_dirs=['./openexr.install/include/OpenEXR', - './openexr.install/include/Imath',], - libraries=libs, - extra_compile_args=extra_compile_args, - extra_link_args=extra_link_args, - ) - ], - py_modules=['Imath'], -) diff --git a/src/wrappers/python/tests/test_minimal.py b/src/wrappers/python/tests/test_minimal.py index 564bab0b74..43560a8560 100644 --- a/src/wrappers/python/tests/test_minimal.py +++ b/src/wrappers/python/tests/test_minimal.py @@ -1,5 +1,9 @@ -import pytest +#!/usr/bin/env python3 + +# SPDX-License-Identifier: BSD-3-Clause +# Copyright (c) Contributors to the OpenEXR Project. +import pytest def test_import(): import OpenEXR diff --git a/src/wrappers/python/tests/test_unittest.py b/src/wrappers/python/tests/test_unittest.py index 5061ac2b91..e08a44b0b9 100644 --- a/src/wrappers/python/tests/test_unittest.py +++ b/src/wrappers/python/tests/test_unittest.py @@ -11,10 +11,8 @@ import random from array import array -import Imath import OpenEXR - -test_dir = os.path.dirname(os.path.abspath(__file__)) +import Imath FLOAT = Imath.PixelType(Imath.PixelType.FLOAT) UINT = Imath.PixelType(Imath.PixelType.UINT) diff --git a/website/API.rst b/website/API.rst index d2fce2bbed..fbaae7e513 100644 --- a/website/API.rst +++ b/website/API.rst @@ -7,6 +7,37 @@ The OpenEXR API ############### +There are two separate, independent APIs for reading and writing EXR +image files: the traditional C++ API, and the newer C API, referred to +as ``OpenEXRCore``. The C++ API is the original, widely-used interface +first released in 2003. It consists of the ``OpenEXR``, ``Iex``, and +``IlmThread`` libraries, with functions and classes in the ``Imf::`` +namespace (or "image format", the original the name of the library at +ILM prior to its public release). + +The ``OpenEXRCore`` library, written primarily by Kimball Thurston at +Weta Digital, was introduced in July, 2021. This C-language +implementation of the file format is the result of a significant +re-thinking of image file I/O and access to image data. The +``OpenEXRCore`` library provides thread-safe, non-blocking access to +files, which was not possible with the older C++ API, where the +framebuffer management is separate from read requests. This new +low-level API allows applications to do custom unpacking of EXR data, +such as on the GPU, while still benefiting from efficient I/O, file +validation, and other semantics. It provides efficient direct access +to EXR files in texturing applications. This C library also introduces +an easier path to implementing OpenEXR bindings in other languages, +such as Rust. + +Currently, the two libraries and APIs sit alongside each other, +although in future OpenEXR releases, the C++ API will migrate to use +the new core in stages. It is not the intention to entirely deprecate +the C++ API, nor must all applications re-implement EXR I/O in terms +of the C library. The C API does not, and will not, provide the rich +set of utility classes that exist in the C++ layer. The +``OpenEXRCore`` library simply offers new functionality for specialty +applications seeking the highest possible performance. + .. toctree:: :caption: API :maxdepth: 2 diff --git a/website/DeepIDsSpecification.rst b/website/DeepIDsSpecification.rst new file mode 100644 index 0000000000..9b4d6fb1e1 --- /dev/null +++ b/website/DeepIDsSpecification.rst @@ -0,0 +1,407 @@ + +.. + SPDX-License-Identifier: BSD-3-Clause + Copyright Contributors to the OpenEXR Project. + +OpenEXR Deep IDs Specification +############################## + + +Introduction +============ + +Deep IDs are primarily used in compositing applications to select +objects in images: + +- The 3D renderer stores multiple ids per + pixel in the final OpenEXR file as well a scene manifest providing a mapping to a + human-readable identifier, such as an object or material name. +- The compositing application reads in the deep IDs and implements a + UI for the artist to create a selection based on the manifest data. +- The selected IDs will then be used to create a deep (per-sample) or + shallow (per-pixel) mask for later use. + +Typical uses include: + +- Grading selected objects. +- Removing a certain percentage of particles from a snow or dust motes + render. +- Adding consistent variations to different individuals in a crowd or to different + trees in a forest by applying an ID-based color correction. + +Deep IDs can also be used for debugging: + +- To identify an object/material which renders incorrectly. +- To collect all visible IDs over a sequence so as to prune scene + objects that are never on screen. + +Pros +---- + +- Each pixel can contain an arbitrary number of IDs (0 included), + allowing for perfect mattes. +- Deep IDs are combined with transparency data to support anti-aliasing, + motion blur and depth of field. +- A deep image stores the color of the individual objects as well as their + individual alpha, so an object extracted by ID will have the correct color + and alpha values along the edges. + +Cons +---- + +- IDs cannot be directly inspected with a simple image viewer. +- Hash collisions can make multiple objects map to the same ID. +- DeepIDs require software to support deepscanline and/or deeptile OpenEXR image types. + + + +Cryptomatte comparison +---------------------- + +Cryptomatte [1]_ is widely adopted ID scheme similar to that described here, with a few differences: + +- Cryptomatte uses a similar concept of a manifest, which is stored as JSON in a string attribute. +- Cryptomatte allocates a fixed size array for IDs, imposing a maximum number of IDs in one pixel. + Setting this too large requires a lot of memory; setting it too small can cause + noise if an object is selected which has been discarded from some pixels. +- Cryptomatte uses the ``scanlineimage`` or ``tiledimage`` EXR format, rather than ``deepscanline`` or ``deeptile`` + to simplify adoption by applications already supporting these EXR types. + Similarly, IDs are stored in the ``R`` and ``B`` channels as FLOAT rather than UINT types, + though are intended to be interpreted as UINT. Although these + channels are named as color channels, care must be taken not to color manage or otherwise modify them, + and tools which re-write Cryptomatte files should ensure they are written as FLOAT rather than HALF. +- Cryptomatte allows slightly less than 2\ :sup:`32` different IDs; the Deep ID scheme allows for 2\ :sup:`32` or 2\ :sup:`64` different IDs. + This reduces the chance of a hash collision. +- Cryptomatte only stores *coverage* of each object. This can be used to generate a mask for an object to apply + a selective grade. Because the depth ordering, transparency and color of each object is not stored, objects isolated + using Cryptomatte will have contaminated edge colors and incorrect alpha values. + +Comparisons of typical images suggest Cryptomatte and deep images with IDs are similar size on disk, +even though deep images carry more information, +but generally deep images are processed faster since they take less space when decompressed. + +It is possible to convert a Deep ID image into a Cryptomatte image, which may be a path to adoption for +tools which read Cryptomatte but not Deep IDs. Conversion in the opposite direction is also possible, +but the resultant deep ID image can only be used for coverage computation. + +As such, the Deep ID approach can be considered an evolution of Cryptomatte, supporting and extending all the features of Cryptomatte, +but with a more formal specification and flexible storage. + + +Deep ID Basics +============== + + +The deep OpenEXR files need to contain the following elements: + +- Deep IDs stored using one or two ``Imf::UINT`` (``uint32_t``) + channels: + + - A single channel stores 32 bit hashes. + - A pair of channels store 64 bit hashes. + +- Manifest data stored in the file’s metadata as an attribute, or in a side-car file. + + - **NOTE**: OpenEXR 3.0+ provides a data structure + and attribute for efficient storage. For more details see :ref:`idmanifest-label` + + +Sample storage +============== + + +Terminology +----------- + +- **Channel**: Every deep image contains many named channels (``R``, + ``G``, ``B``, ``A``, ``Z``, ``id``, etc) +- **Pixel**: A discrete image element containing 0 or more deep + ``samples``. +- **Sample**: A container storing the value of each channel at a + specific depth. +- **Id**: A unique numerical identifier mapping to an ``item``. +- **Kind**: a named ``id`` category (model name, shader name, asset management URI, etc). +- **Manifest**: a data structure mapping each id to one ``item``. +- **Item**: an entity (object, material, etc) represented by: + + - Artist friendly strings in the manifest, e.g. “Chair16” (one string of each ``kind``) + - A collection of ``samples`` matching a particular ``id`` in the image. + +Principles +---------- + +1. Deep IDs have a single ``id`` of each ``kind`` per ``sample``, and + every ``item`` in the image has a consistent ``id`` for all its + ``samples``. +2. If two different ``items`` overlap the same space, they will be + stored in separate ``samples`` (which themselves may or may not + overlap). +3. Some ``id`` may not have an associated string, like ``particleid``, + and still be useful (for example through image-based picking). +4. In complex cases like an instanced particle system, each ``sample`` + may have an ``instanceid``, ``particleid``, and ``id``/``objectid``. + +Standard ID Channel names +------------------------- + +============== ====================================== +Name Contents +============== ====================================== +**id** by default, an object identifier [2]_ +**objectid** identifier of an object +**materialid** identifier of an material +**particleid** identifier of a particle +**instanceid** identifier of an instanced object +============== ====================================== + + +To limit the risk of hash collision, a ``uint64_t`` bits can be encoded +with two ``uint32_t`` channels. The convention is then to suffix the +channel name with ``0`` or ``1`` to indicate the channels storing the +least and the most significant 32 bits respectively, +e.g. ``particleid0`` and ``particleid1`` or ``particle.id0`` and ``particle.id1`` + +When sorted alphanumerically, the channel storing the most significant bits should appear immediately +after the channel storing the least significant bits. See appendix for details. + +ID generation +------------- + +Any non-uint32 identifier can be hashed to generate an id. + +- ``uint32_t`` hashes can be generated with ``MurmurHash3_x86_32``. +- ``uint64_t`` hashes can be generated with the top 8 bytes of + ``MurmurHash3_x64_128``. + +OpenEXR 3.x offers two convenience functions to hash strings [3]_: +``IDManifest::MurmurHash32`` and ``IDManifest::MurmurHash64``. + +The Deep ID scheme does not require these hash schemes to be used. For example if an asset management +system already generates hashes, those can be used instead. It is also not required to use a hash scheme +to map between names and numeric identifiers. For example the IDs could be generated in the order that they +are stored in the source geometry file. Such an approach would avoid hash collisions, +but would not generate IDs consistently across different shots or scenes. + + +Multivariate IDs +---------------- + +Hashing more than one ``kind`` (i.e. object + material) limits storage +requirements without impairing the artist’s ability to generate +arbitrary mattes. + +For examples hashing the object and material names together is common +practice. In that case, a single ``id`` will map to two ``kinds`` in the +manifest, providing more flexibility at the cost of a slightly increased +risk of hash collision. + +Manifest data +------------- + +The manifest contains the human-readable data corresponding to a given +hash. It is a big table of strings that may require more storage than +the actual image data. It can be stored using the following mechanisms: + +.. _idmanifest-label: + +OpenEXR idmanifest container +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Since OpenEXR 3.0, there is a new standard ``idmanifest`` attribute +using a ``CompressedIDManifest`` metadata type, specially designed to +transport manifest data efficiently [4]_. It is optimized to reduce the storage space required, +and is the most standard approach. + +The utility ``exrmanifest`` outputs the manifest of EXR images as plain text. + +Note that use of the ``idmanifest`` attribute is not limited to the Deep ID scheme. +Other schemes - such as Cryptomatte - could also use the attribute to encode manifest +and other related data, to save storage space. +(Indeed, the idmanifest was in part derived from Cryptomatte’s metadata scheme for this purpose) + + +OpenEXR string container +^^^^^^^^^^^^^^^^^^^^^^^^ + +The manifest can be stored in ``string`` or ``stringvector`` attributes, +but this is not very efficient and may significantly increase file size. + +Side-car files +^^^^^^^^^^^^^^ + +Alternatively, the manifest may be stored in a separate file, with an OpenEXR attributes, +a database or a file naming convention used to associate one or more OpenEXR files +with the corresponding sidecar file. Sidecar files can be advantageous because +they can be shared between different images, and also updated as more content is being rendered. + +Such schemes are not supported by the OpenEXR library, nor are they defined here, +since that is outside the scope of the OpenEXR file format specification. +Although sidecar files may be appropriate for temporary usage, it is strongly recommended +that the embedded manifest is used in OpenEXR images which are to be shared between different companies +or for archival usage. + + + +Deep Compositing and Deep IDs +============================= + + +Deep Compositing workflows allow objects to be rendered in separate passes and then merged together as a post process. +For example, it is possible to render a volumetric element such as a smoke, and merge other objects into it. +Deep images containing such volume renders may need to become very large in order to capture all the detail required for the merge to work correctly. +Deep IDs are a powerful addition to deep compositing workflows, as they allow for greater control of individual elements within a single render. +For this to work effectively, the ID passes must be included in the same deep image as that used for deep compositing. +It is tempting to generate two separate deep images for each rendered object, one containing color and one containing the IDs, +particularly since this is a common approach with Cryptomatte workflow. +However, creating separate files complicates deep compositing with deep IDs, since it is harder to associate each individual object's ID with its color. +Very little extra storage is required to add an ID channel to a deep image. + +Deep IDs are still useful where full deep compositing workflows are not used, to allow color correction of individual objects. +In that case, the deep image need not be large. In the case of a volume, a single sample can be used to indicate the volume's ID, color and transparency. +Renderers can produce these efficient passes by combining together adjacent deep samples that have the same set of id channels. +The object color (RGB) channels can also be omitted to reduce file size at the cost of loss of fidelity along object edges. + +Tools which do not support full deep compositing workflows could still support deep images for the limited use of ID selection. +For example an image editing package may have a "load selected objects from deep image" tool which allows the desired object to be selected, +then processes that into a regular non-deep image or mask for editing. + +Example code +============ + +OpenEXR provides two example tools, ``deepidexample`` and ``deepidselect``. +Compiled tools will be found in the ``src/examples`` folder in the build directory. They are not installed. + + +DeepIDExample +------------- + +``deepidexample`` creates a deep image with multiple objects (two different shapes at one of three sizes), +in one of seven colors. It is intended as a tool for generating test sequences and as an example of code +that generates an image with deep IDs and a manifest. + +``deepidexample`` can generate a sequence of frames, to help test that the IDs are consistently +generated and selected. Specify ``--frame`` for the frame number. The animation cycles every 100 frames. +This ``bash`` command generates a sequence of frames: + +.. code:: bash + + for x in `seq 1000 1100` ; do ./deepidexample --frame $x output.$x.deep.exr ; done + +Run ``deepidexample`` to see further options. + + +DeepIDSelect +------------ + +``deepidselect`` selects individual objects within a deep file, and outputs just those objects. +It is intended to serve as an example of parsing idmanifests to find compile a list of IDs which +match a given substring, and using those ids to identify samples. Its usage is not limited solely +to files created by deepidexample; it should handle files with arbitrary channel names and manifests. +deepidselect supports the ``id64`` scheme with the ``--64`` flag. + +In basic usage, specify ``input.deep.exr (matches) output.deep.exr`` + +``matches`` is a list of one or more separate strings. All objects whose names contain any of the +given substring will be included in output.deep.exr (it is a logical OR of the arguments) +The ``--and`` can be used to force matching of (one or more of) the following match as well as the previous. +For example, ``blue --and circle`` will match any object which is both blue, and a circle. +``blue green --and big small --and circle`` will match blue or green objects, +and which are big or small, and which are circles. +This could also be read as `( blue or green ) and ( big or small ) and ( circle )` + +Each match can be limited to a given component name by specifying ``component:match``. +For example ``model:bl`` will match objects whose model is ``blob`` but not ones whose material is ``blue``. +Specifying a channel name followed by a number will select the object by number, rather than by name. +For example, ``particleid:12`` will select the object with particle ID 12. +(Note that this feature means it is not possible to have a purely numeric substring match with this tool) + +``--mask`` outputs a shallow single channel image which indicating the relative coverage of each pixel +for the selected object. For schemes where the deep image only contains ID (and alpha) information, +but does not store color, this can be used to grade only the selected object. +Edge contamination may be observed along transparent edges of a selected object, if an object behind it is not selected. + +To keep the code simple, ``deepidselect`` is only a minimal example of string matching against ID manifests. +For example, it doesn't support regular expressions, or more advanced Boolean logic including negative matches. + + +Appendix +======== + + +64 to 2 x 32 bits conversion and back +------------------------------------- + +To limit the risk of hash collision, a ``uint64_t`` can be encoded in 2 +``uint32_t`` channels, like ``materialid`` and ``materialid2``, using +little-endian byte ordering. + +.. code:: cpp + + #include + #include + #include + + int main() + { + using namespace std; + + // uint 64 input + uint64_t x = 0x12345678'87654321ULL; + cout << setw(20) << "uint64 input: " << hex << x << endl; + + // Convert one uint 64 -> two uint 32 + uint32_t lo = uint32_t(x); + uint32_t hi = uint32_t(x >> 32); + cout << setw(20) << "uint32 low: " << hex << lo << " high: " << hi << endl; + + // Convert two uint32 -> one uint64 + uint64_t y = (uint64_t(hi) << 32) | lo; + cout << setw(20) << "uint64 recombined: " << hex << y << endl; + } + +Output: + +:: + + uint64 input: 1234567887654321 + uint32 low: 87654321 high: 12345678 + uint64 recombined: 1234567887654321 + +Computing a shallow mask from Deep IDs +-------------------------------------- + +A shallow mask is a pixel-level mask that can be used with non-deep +compositing operators. + +Here is the pseudo-code to correctly compute an ID selection mask for a +single pixel: + +.. code:: python + + total_combined_alpha = 0.0 + mask_alpha = 0.0 + sorted_pixel = sort_pixel_front_to_back(input_pixel) + + foreach(sample in sorted_pixel): + if id_is_in_selection(sample.id): + mask_alpha += sample.alpha * (1.0 - total_combined_alpha) + total_combined_alpha += sample.alpha * (1.0 - total_combined_alpha) + + if total_combined_alpha == 0.0: + return 0.0 + else: + return mask_alpha / total_combined_alpha + +.. [1] + See `Cryptomatte `__ on github + +.. [2] + See `OpenEXR reserved channel + names `__. + +.. [3] + See + `ImfIDManifest `__ + +.. [4] + For more details of the compression scheme used by idmanifest, see `A scheme for storing object ID manifests in openEXR images` In Proceedings of the 8th Annual Digital Production Symposium (DigiPro '18). Association for Computing Machinery, New York, NY, USA, Article 9, 1–8. https://doi.org/10.1145/3233085.3233086 diff --git a/website/HelloWorld.rst b/website/HelloWorld.rst index bb5846bedb..d2ef782148 100644 --- a/website/HelloWorld.rst +++ b/website/HelloWorld.rst @@ -11,33 +11,40 @@ Hello, World :caption: Hello, World :maxdepth: 1 -A simple program to write a simple ``.exr`` file of an image of 10x10 -pixels with values that are a ramp in green and blue: +Write an Image +============== -.. literalinclude:: src/writer/writer.cpp +This example :download:`exrwriter.cpp ` +program writes a simple ``hello.exr`` file of an image of 10x10 pixels with +values that are a ramp in green and blue: -And the ``CMakeLists.txt`` file to build: +.. literalinclude:: src/exrwriter/exrwriter.cpp + +And the :download:`CMakeLists.txt ` file to build: -.. literalinclude:: src/writer/CMakeLists.txt +.. literalinclude:: src/exrwriter/CMakeLists.txt To build: -.. literalinclude:: src/writer/build.sh +.. literalinclude:: src/exrwriter/build.sh For more details, see :ref:`The OpenEXR API`. -And a simple program to read an ``.exr`` file: +Read an Image +============= -.. literalinclude:: src/reader/reader.cpp +This companion example :download:`exrreader.cpp ` +program reads the ``hello.exr`` file written by the writer program above: +.. literalinclude:: src/exrreader/exrreader.cpp -And the ``CMakeLists.txt`` file to build: +And the :download:`CMakeLists.txt ` file to build: -.. literalinclude:: src/reader/CMakeLists.txt +.. literalinclude:: src/exrreader/CMakeLists.txt To build: -.. literalinclude:: src/reader/build.sh +.. literalinclude:: src/exrreader/build.sh diff --git a/website/OpenEXRFileLayout.rst b/website/OpenEXRFileLayout.rst index d028b11151..5a054fb95b 100644 --- a/website/OpenEXRFileLayout.rst +++ b/website/OpenEXRFileLayout.rst @@ -991,177 +991,858 @@ pixels. The image has two channels: G, of type ``HALF``, and Z, of type ``FLOAT``. The pixel data are not compressed. The entire file is 415 bytes long. -The first line of text in each of the gray boxes below lists up to 16 -bytes of the file in hexadecimal notation. The second line in each box -shows how the bytes are grouped into integers, floating-point numbers -and text strings. The third and fourth lines indicate how those basic -objects form compound objects such as attributes or the line offset -table. - -.. code-block:: - - 76 2f 31 01 02 00 00 00 63 68 61 6e 6e 65 6c 73 - 20000630 | 2 | c h a n n e l s - magic number | version, flags | attribute name - | | start of header -.. code-block:: - - 00 63 68 6c 69 73 74 00 25 00 00 00 47 00 01 00 - \0 | c h l i s t \0 | 37 | G \0 | HALF - | attribute type | attribute size | attribute value - -.. code-block:: - - 00 00 00 00 00 00 01 00 00 00 01 00 00 00 5a 00 - | 0 | 0 | 1 | 1 | Z \0 | - - -.. code-block:: - - 02 00 00 00 00 00 00 00 01 00 00 00 01 00 00 00 - FLOAT | 0 | 0 | 1 | 1 | - | -.. code-block:: - - 00 63 6f 6d 70 72 65 73 73 69 6f 6e 00 63 6f 6d - \0 | c o m p r e s s i o n \0 | c o m - | attribute name | attribute type - -.. code-block:: - - 70 72 65 73 73 69 6f 6e 00 01 00 00 00 00 64 61 - p r e s s i o n \0 | 1 | NONE| d a - | attribute size |value| - -.. code-block:: - - 74 61 57 69 6e 64 6f 77 00 62 6f 78 32 69 00 10 - t a W i n d o w \0 | b o x 2 i \0 | - attribute name | attribute type | - -.. code-block:: - - 00 00 00 00 00 00 00 00 00 00 00 03 00 00 00 02 - 16 | 0 | 0 | 3 | - attribute size| attribute value - -.. code-block:: - - 00 00 00 64 69 73 70 6c 61 79 57 69 6e 64 6f 77 - 2 | d i s p l a y W i n d o w - | attribute name - -.. code-block:: - - 00 62 6f 78 32 69 00 10 00 00 00 00 00 00 00 00 - \0 | b o x 2 i \0 | 16 | 0 | - | attribute type | attribute size | attribute value - - -.. code-block:: - - 00 00 00 03 00 00 00 02 00 00 00 6c 69 6e 65 4f - 0 | 3 | 2 | l i n e O - | attribute name - -.. code-block:: - - 72 64 65 72 00 6c 69 6e 65 4f 72 64 65 72 00 01 - r d e r \0 | l i n e O r d e r \0 | - | attribute type | - -.. code-block:: - - 00 00 00 00 70 69 78 65 6c 41 73 70 65 63 74 52 - 1 |INCY | p i x e l A s p e c t R - attribute size|value| attribute name - -.. code-block:: - - 61 74 69 6f 00 66 6c 6f 61 74 00 04 00 00 00 00 - a t i o \0 | f l o a t \0 | 4 | - | attribute type | attribute size | - - -.. code-block:: - - 00 80 3f 73 63 72 65 65 6e 57 69 6e 64 6f 77 43 - 1.0 | s c r e e n W i n d o w C - attribute value| attribute name - - -.. code-block:: - - 65 6e 74 65 72 00 76 32 66 00 08 00 00 00 00 00 - e n t e r \0 | v 2 f \0 | 8 | - | attribute type | attribute size | - - -.. code-block:: - - 00 00 00 00 00 00 73 63 72 65 65 6e 57 69 6e 64 - 0.0 | 0.0 | s c r e e n W i n d - attribute value | attribute name - - -.. code-block:: - - 6f 77 57 69 64 74 68 00 66 6c 6f 61 74 00 04 00 - o w W i d t h \0 | f l o a t \0 | - | attribute type | - - -.. code-block:: - - 00 00 00 00 80 3f 00 3f 01 00 00 00 00 00 00 5f - 4 | 1.0 | \0 | 319 | - size | attribute value | | offset of scan line 0 | - end of header | start of scan line offset table - -.. code-block:: - - 01 00 00 00 00 00 00 7f 01 00 00 00 00 00 00 00 - 351 | 383 | - offset of scan line 1 | offset of scan line 2 | - end of scan line offset table | - -.. code-block:: - - 00 00 00 18 00 00 00 00 00 54 29 d5 35 e8 2d 5c - 0 | 24 | 0.000 | 0.042 | 0.365 | 0.092 | - y | pixel data size | pixel data for G channel | - scan line 0 - -.. code-block:: - - 28 81 3a cf e1 34 3e 8b 0b bb 3d 89 74 f9 3e 01 - 0.000985395 | 0.176643 | 0.0913306 | 0.487217 | - pixel data for Z channel | - | -.. code-block:: - - 00 00 00 18 00 00 00 37 38 76 33 74 3b 73 38 7f - 1 | 24 | 0.527 | 0.233 | 0.932 | 0.556 | - y | pixel data size | pixel data for G channel | - scan line 1 - -.. code-block:: - - ab e8 3e 8a cf 54 3f 5b 6c 11 3f 20 35 50 3d 02 - 0.454433 | 0.831292 | 0.56806 | 0.0508319 | - pixel data for Z channel | - | - -.. code-block:: - - 00 00 00 18 00 00 00 23 3a 0a 34 02 3b 5d 3b 38 - 2 | 24 | 0.767 | 0.252 | 0.876 | 0.920 | - y | pixel data size | pixel data for G channel | - scan line 2 - -.. code-block:: - - f3 9a 3c 4d ad 98 3e 1c 14 08 3f 4c f3 03 3f - 0.0189148 | 0.298197 | 0.531557 | 0.515431 - pixel data for Z channel - end of file +The first column of the table below lists all the bytes of the file in hexadecimal +notation. The second column of the table shows how the bytes are grouped into +integers, floating-point numbers and text strings. The third column of the table +indicate how those basic objects form compound objects such as attributes +or the line offset table. + +Download the :download:`sample.exr `. + +.. table:: + :width: 50% + + +-----------+------------+-----------------------+ + |byte |value |description | + +===========+============+=======================+ + |76 |20000630 |``magic number`` | + +-----------+ | | + |2f | | | + +-----------+ | | + |31 | | | + +-----------+ | | + |01 | | | + +-----------+------------+-----------------------+ + |02 |2 |``version, flags`` | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+-----------------------+ + | Start of header | + +-----------+------------+-----------------------+ + |63 |c |attribute | + +-----------+------------+name: | + |68 |h |``channels`` | + +-----------+------------+ | + |61 |a | | + +-----------+------------+ | + |6e |n | | + +-----------+------------+ | + |6e |n | | + +-----------+------------+ | + |65 |e | | + +-----------+------------+ | + |6c |l | | + +-----------+------------+ | + |73 |s | | + +-----------+------------+ | + |00 |\\0 | | + +-----------+------------+-----------------------+ + |63 |c |attribute | + +-----------+------------+type: | + |68 |h |``chlist`` | + +-----------+------------+ | + |6c |l | | + +-----------+------------+ | + |69 |i | | + +-----------+------------+ | + |73 |s | | + +-----------+------------+ | + |74 |t | | + +-----------+------------+ | + |00 |\\0 | | + +-----------+------------+-----------------------+ + |25 |37 |attribute | + +-----------+ |size | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+-----------------------+ + |47 |G |G channel | + +-----------+ |struct values | + |00 |\\0 | | + +-----------+------------+ | + |01 |type: HALF | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+ | + |00 |pLinear:0 | | + +-----------+------------+ | + |00 |0 | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+ | + |01 |xSampling: 1| | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+ | + |01 |ySampling: 1| | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+-----------------------+ + |5a |Z |Z channel | + +-----------+ |struct values | + |00 |\\0 | | + +-----------+------------+ | + |02 |type: FLOAT | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+ | + |00 |pLinear: 0 | | + +-----------+------------+ | + |00 |0 | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+ | + |01 |xSampling: 1| | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+ | + |01 |ySampling: 1| | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+-----------------------+ + |00 |\\0 | | + +-----------+------------+-----------------------+ + |63 |c |attribute | + +-----------+------------+name: | + |6f |o |``compression`` | + +-----------+------------+ | + |6d |m | | + +-----------+------------+ | + |70 |p | | + +-----------+------------+ | + |72 |r | | + +-----------+------------+ | + |65 |e | | + +-----------+------------+ | + |73 |s | | + +-----------+------------+ | + |73 |s | | + +-----------+------------+ | + |69 |i | | + +-----------+------------+ | + |6f |o | | + +-----------+------------+ | + |6e |n | | + +-----------+------------+ | + |00 |\\0 | | + +-----------+------------+-----------------------+ + |63 |c |attribute | + +-----------+------------+type: | + |6f |o |``compression`` | + +-----------+------------+ | + |6d |m | | + +-----------+------------+ | + |70 |p | | + +-----------+------------+ | + |72 |r | | + +-----------+------------+ | + |65 |e | | + +-----------+------------+ | + |73 |s | | + +-----------+------------+ | + |73 |s | | + +-----------+------------+ | + |69 |i | | + +-----------+------------+ | + |6f |o | | + +-----------+------------+ | + |6e |n | | + +-----------+------------+ | + |00 |\\0 | | + +-----------+------------+-----------------------+ + |01 |1 |attribute | + +-----------+ |size | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+-----------------------+ + |00 | None |attribute | + | | |value | + +-----------+------------+-----------------------+ + |64 |d |attribute | + +-----------+------------+name: | + |61 |a |``dataWindow`` | + +-----------+------------+ | + |74 |t | | + +-----------+------------+ | + |61 |a | | + +-----------+------------+ | + |57 |W | | + +-----------+------------+ | + |69 |i | | + +-----------+------------+ | + |6e |n | | + +-----------+------------+ | + |64 |d | | + +-----------+------------+ | + |6f |o | | + +-----------+------------+ | + |77 |w | | + +-----------+------------+ | + |00 |\\0 | | + +-----------+------------+-----------------------+ + |62 |b |attribute | + +-----------+------------+type: ``box2i`` | + |6f |o | | + +-----------+------------+ | + |78 |x | | + +-----------+------------+ | + |32 |2 | | + +-----------+------------+ | + |69 |i | | + +-----------+------------+ | + |00 |\\0 | | + +-----------+------------+-----------------------+ + |10 |16 |attribute | + +-----------+ |size | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+-----------------------+ + |00 |0 | | + +-----------+(box.min.x) | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+-----------------------+ + |00 |0 | | + +-----------+(box.min.y) | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+-----------------------+ + |03 |3 | | + +-----------+(box.max.x) | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+-----------------------+ + |02 |2 | | + +-----------+(box.max.y) | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+-----------------------+ + |64 |d |attribute | + +-----------+------------+name: | + |69 |i |``displayWindow`` | + +-----------+------------+ | + |73 |s | | + +-----------+------------+ | + |70 |p | | + +-----------+------------+ | + |6c |l | | + +-----------+------------+ | + |61 |a | | + +-----------+------------+ | + |79 |y | | + +-----------+------------+ | + |57 |W | | + +-----------+------------+ | + |69 |i | | + +-----------+------------+ | + |6e |n | | + +-----------+------------+ | + |64 |d | | + +-----------+------------+ | + |6f |o | | + +-----------+------------+ | + |77 |w | | + +-----------+------------+ | + |00 |\\0 | | + +-----------+------------+-----------------------+ + |62 |b |attribute | + +-----------+------------+type: ``box2i`` | + |6f |o | | + +-----------+------------+ | + |78 |x | | + +-----------+------------+ | + |32 |2 | | + +-----------+------------+ | + |69 |i | | + +-----------+------------+ | + |00 |\\0 | | + +-----------+------------+-----------------------+ + |10 |16 |attribute | + +-----------+ |size | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+-----------------------+ + |00 |0 | | + +-----------+(box.min.x) | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+ + + |00 |0 | | + +-----------+(box.min.y) | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+-----------------------+ + |03 |3 | | + +-----------+(box.max.x) | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+ + + |02 |2 | | + +-----------+(box.max.y) | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+-----------------------+ + |6c |l |attribute | + +-----------+------------+name: | + |69 |i |``lineOrder`` | + +-----------+------------+ | + |6e |n | | + +-----------+------------+ | + |65 |e | | + +-----------+------------+ | + |4f |O | | + +-----------+------------+ | + |72 |r | | + +-----------+------------+ | + |64 |d | | + +-----------+------------+ | + |65 |e | | + +-----------+------------+ | + |72 |r | | + +-----------+------------+ | + |00 |\\0 | | + +-----------+------------+-----------------------+ + |6c |l |attribute | + +-----------+------------+type: ``lineOrder`` | + |69 |i | | + +-----------+------------+ | + |6e |n | | + +-----------+------------+ | + |65 |e | | + +-----------+------------+ | + |4f |O | | + +-----------+------------+ | + |72 |r | | + +-----------+------------+ | + |64 |d | | + +-----------+------------+ | + |65 |e | | + +-----------+------------+ | + |72 |r | | + +-----------+------------+ | + |00 |\\0 | | + +-----------+------------+-----------------------+ + |01 |1 |attribute | + +-----------+ |size | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+-----------------------+ + |00 |0 |INCY value | + +-----------+------------+-----------------------+ + |70 |p |attribute | + +-----------+------------+name: | + |69 |i |``pixelAspectRatio`` | + +-----------+------------+ | + |78 |x | | + +-----------+------------+ | + |65 |e | | + +-----------+------------+ | + |6c |l | | + +-----------+------------+ | + |41 |A | | + +-----------+------------+ | + |73 |s | | + +-----------+------------+ | + |70 |p | | + +-----------+------------+ | + |65 |e | | + +-----------+------------+ | + |63 |c | | + +-----------+------------+ | + |74 |t | | + +-----------+------------+ | + |52 |R | | + +-----------+------------+ | + |61 |a | | + +-----------+------------+ | + |74 |t | | + +-----------+------------+ | + |69 |i | | + +-----------+------------+ | + |6f |o | | + +-----------+------------+ | + |00 |\\0 | | + +-----------+------------+-----------------------+ + |66 |f |attribute | + +-----------+------------+type: ``float`` | + |6c |l | | + +-----------+------------+ | + |6f |o | | + +-----------+------------+ | + |61 |a | | + +-----------+------------+ | + |74 |t | | + +-----------+------------+ | + |00 |\\0 | | + +-----------+------------+-----------------------+ + |04 |4 |attribute | + +-----------+ |size | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+-----------------------+ + |00 |1.0 |attribute | + +-----------+ |value | + |00 | | | + +-----------+ | | + |80 | | | + +-----------+ | | + |3f | | | + +-----------+------------+-----------------------+ + |73 |s |attribute | + +-----------+------------+name: | + |63 |c |``screenWindowCenter`` | + +-----------+------------+ | + |72 |r | | + +-----------+------------+ | + |65 |e | | + +-----------+------------+ | + |65 |e | | + +-----------+------------+ | + |6e |n | | + +-----------+------------+ | + |57 |W | | + +-----------+------------+ | + |69 |i | | + +-----------+------------+ | + |6e |n | | + +-----------+------------+ | + |64 |d | | + +-----------+------------+ | + |6f |o | | + +-----------+------------+ | + |77 |w | | + +-----------+------------+ | + |43 |C | | + +-----------+------------+ | + |65 |e | | + +-----------+------------+ | + |6e |n | | + +-----------+------------+ | + |74 |t | | + +-----------+------------+ | + |65 |e | | + +-----------+------------+ | + |72 |r | | + +-----------+------------+ | + |00 |\\0 | | + +-----------+------------+-----------------------+ + |76 |v |attribute | + +-----------+------------+type:``v2f`` | + |32 |2 | | + +-----------+------------+ | + |66 |f | | + +-----------+------------+ | + |00 |\\0 | | + +-----------+------------+-----------------------+ + |08 |8 |attribute | + +-----------+ |size | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+-----------------------+ + |00 |0.0 |attribute | + +-----------+ |value: | + |00 | |v2f(0.0, 0.0) | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+ | + |00 |0.0 | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+-----------------------+ + |73 |s |attribute | + +-----------+------------+name: | + |63 |c |``screenWindowWidth`` | + +-----------+------------+ | + |72 |r | | + +-----------+------------+ | + |65 |e | | + +-----------+------------+ | + |65 |e | | + +-----------+------------+ | + |6e |n | | + +-----------+------------+ | + |57 |W | | + +-----------+------------+ | + |69 |i | | + +-----------+------------+ | + |6e |n | | + +-----------+------------+ | + |64 |d | | + +-----------+------------+ | + |6f |o | | + +-----------+------------+ | + |77 |w | | + +-----------+------------+ | + |57 |W | | + +-----------+------------+ | + |69 |i | | + +-----------+------------+ | + |64 |d | | + +-----------+------------+ | + |74 |t | | + +-----------+------------+ | + |68 |h | | + +-----------+------------+ | + |00 |\\0 | | + +-----------+------------+-----------------------+ + |66 |f |attribute | + +-----------+------------+type: ``float`` | + |6c |l | | + +-----------+------------+ | + |6f |o | | + +-----------+------------+ | + |61 |a | | + +-----------+------------+ | + |74 |t | | + +-----------+------------+ | + |00 |\\0 | | + +-----------+------------+-----------------------+ + |04 |4 |size | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+-----------------------+ + |00 |1.0 |attribute | + +-----------+ |value | + |00 | | | + +-----------+ | | + |80 | | | + +-----------+ | | + |3f | | | + +-----------+------------+-----------------------+ + |00 |\\0 - end of header | + +-----------+------------+-----------------------+ + |End of header | + +-----------+------------+-----------------------+ + |Start of scan line offset table | + +-----------+------------+-----------------------+ + |3f |319 |offset of | + +-----------+ |scan line 0 | + |01 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+-----------------------+ + |5f |351 |offset of | + +-----------+ |scan line 1 | + |01 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+-----------------------+ + |7f |383 |offset of | + +-----------+ |scan line 2 | + |01 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+-----------------------+ + |End of scan line offset table | + +-----------+------------+-----------------------+ + |00 |0 |y scan line 0 | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+-----------------------+ + |18 |24 |pixel data | + +-----------+ |size | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+-----------------------+ + |00 |0.000 |pixel data | + +-----------+ |for G | + |00 | |channel | + +-----------+------------+ + + |54 |0.042 | | + +-----------+ | | + |29 | | | + +-----------+------------+ + + |d5 |0.365 | | + +-----------+ | | + |35 | | | + +-----------+------------+ + + |e8 |0.092 | | + +-----------+ | | + |2d | | | + +-----------+------------+-----------------------+ + |5c |0.000985395 |pixel data | + +-----------+ |for Z | + |28 | |channel | + +-----------+ | | + |81 | | | + +-----------+ | | + |3a | | | + +-----------+------------+ | + |cf |0.176643 | | + +-----------+ | | + |e1 | | | + +-----------+ | | + |34 | | | + +-----------+ | | + |3e | | | + +-----------+------------+ + + |8b |0.0913306 | | + +-----------+ | | + |0b | | | + +-----------+ | | + |bb | | | + +-----------+ | | + |3d | | | + +-----------+------------+ + + |89 |0.487217 | | + +-----------+ | | + |74 | | | + +-----------+ | | + |f9 | | | + +-----------+ | | + |3e | | | + +-----------+------------+-----------------------+ + |01 |1 |y scan line 1 | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+-----------------------+ + |18 |24 |pixel data | + +-----------+ |size | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+-----------------------+ + |37 |0.527 |pixel data | + +-----------+ |for G | + |38 | |channel | + +-----------+------------+ + + |76 |0.233 | | + +-----------+ | | + |33 | | | + +-----------+------------+ + + |74 |0.932 | | + +-----------+ | | + |3b | | | + +-----------+------------+ + + |73 |0.556 | | + +-----------+ | | + |38 | | | + +-----------+------------+-----------------------+ + |7f |0.454433 |pixel data | + +-----------+ |for Z | + |ab | |channel | + +-----------+ | | + |e8 | | | + +-----------+ | | + |3e | | | + +-----------+------------+ | + |8a |0.831292 | | + +-----------+ | | + |cf | | | + +-----------+ | | + |54 | | | + +-----------+ | | + |3f | | | + +-----------+------------+ + + |5b |0.56806 | | + +-----------+ | | + |6c | | | + +-----------+ | | + |11 | | | + +-----------+ | | + |3f | | | + +-----------+------------+ + + |20 |0.0508319 | | + +-----------+ | | + |35 | | | + +-----------+ | | + |50 | | | + +-----------+ | | + |3d | | | + +-----------+------------+-----------------------+ + |02 |2 |y scan line 2 | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+-----------------------+ + |18 |24 |pixel data | + +-----------+ |size | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+ | | + |00 | | | + +-----------+------------+-----------------------+ + |23 |0.767 |pixel data | + +-----------+ |for G | + |3a | |channel | + +-----------+------------+ + + |0a |0.252 | | + +-----------+ | | + |34 | | | + +-----------+------------+ + + |02 |0.876 | | + +-----------+ | | + |3b | | | + +-----------+------------+ + + |5d |0.920 | | + +-----------+ | | + |3b | | | + +-----------+------------+-----------------------+ + |38 |0.0189148 |pixel data | + +-----------+ |for Z | + |f3 | |channel | + +-----------+ | | + |9a | | | + +-----------+ | | + |3c | | | + +-----------+------------+ | + |4d |0.298197 | | + +-----------+ | | + |ad | | | + +-----------+ | | + |98 | | | + +-----------+ | | + |3e | | | + +-----------+------------+ | + |1c |0.531557 | | + +-----------+ | | + |14 | | | + +-----------+ | | + |08 | | | + +-----------+ | | + |3f | | | + +-----------+------------+ | + |4c |0.515431 | | + +-----------+ | | + |f3 | | | + +-----------+ | | + |03 | | | + +-----------+ + | + |3f | | | + +-----------+------------+-----------------------+ + |End of file | + +-----------+------------+-----------------------+ diff --git a/website/ReadingAndWritingImageFiles.rst b/website/ReadingAndWritingImageFiles.rst index 432e04cf9b..d34d5aa35a 100644 --- a/website/ReadingAndWritingImageFiles.rst +++ b/website/ReadingAndWritingImageFiles.rst @@ -124,7 +124,11 @@ Writing an RGBA Image File Writing a simple RGBA image file is fairly straightforward: .. literalinclude:: src/writeRgba1.cpp + :language: c++ :linenos: + :dedent: + :start-after: [begin writeRgba1] + :end-before: [end writeRgba1] Construction of an RgbaOutputFile object, on line 4, creates an OpenEXR header, sets the header's attributes, opens the file with the specified name, and stores @@ -137,16 +141,13 @@ example, the ``pixels`` pointer is assumed to point to the beginning of an array of ``width*height`` pixels. The pixels are represented as ``Rgba`` structs, which are defined like this: -.. code-block:: +.. literalinclude:: src/structDefinitions.cpp + :language: c++ :linenos: + :dedent: + :start-after: [Rgba definition begin] + :end-before: [Rgba definition end] - struct Rgba - { - half r; // red - half g; // green - half b; // blue - half a; // alpha (opacity) - }; The elements of our array are arranged so that the pixels of each scan line are contiguous in memory. The ``setFrameBuffer()`` function takes @@ -191,17 +192,12 @@ to get right than with error return values. For instance, a program that calls our ``writeRgba1()`` function can handle all possible error conditions with a single try/catch block: -.. code-block:: +.. literalinclude:: src/writeRgba1.cpp + :language: c++ :linenos: - - try - { - writeRgba1 (fileName, pixels, width, height); - } - catch (const std::exception &exc) - { - std::cerr << exc.what() << std::endl; - } + :dedent: + :start-after: [begin tryCatchExample] + :end-before: [end tryCatchExample] Writing a Cropped RGBA Image ---------------------------- @@ -215,7 +211,10 @@ the data window specifies the region for which valid pixel data exist. Only the pixels in the data window are stored in the file. .. literalinclude:: src/writeRgba2.cpp + :language: c++ :linenos: + :start-after: [begin writeRgba2] + :end-before: [end writeRgba2] The code above is similar to that in `Writing an RGBA Image File`_, where the whole image was stored in the file. Two things are different, however: When the @@ -237,12 +236,12 @@ pointing to the pixel at the upper left corner of the data window, at coordinates ``(dataWindow.min.x, dataWindow.min.y)``, the arguments to the ``setFrameBuffer()`` call would have to be to be changed as follows: -.. code-block:: +.. literalinclude:: src/writeRgba2.cpp + :language: c++ :linenos: - - int dwWidth = dataWindow.max.x - dataWindow.min.x + 1; - - file.setFrameBuffer (pixels - dataWindow.min.x - dataWindow.min.y * dwWidth, 1, dwWidth); + :dedent: + :start-after: [begin writeRgba2ResizeFrameBuffer] + :end-before: [end writeRgba2ResizeFrameBuffer] With these settings, evaluation of @@ -395,6 +394,8 @@ those attributes' values. .. literalinclude:: src/readHeader.cpp :language: c++ :linenos: + :start-after: [begin readHeader] + :end-before: [end readHeader] As usual, we open the file by constructing an RgbaInputFile object. Calling ``findTypedAttribute(n)`` searches the header for an @@ -420,32 +421,20 @@ become invalid as soon as the ``RgbaInputFile`` object is destroyed. Therefore, the following will not work: -.. code-block:: +.. literalinclude:: src/readHeader.cpp + :language: c++ :linenos: - - void - readComments (const char fileName[], StringAttribute *&comments) - { - // error: comments pointer is invalid after this function returns - - RgbaInputFile file (fileName); - - comments = file.header().findTypedAttribute ("comments"); - } + :start-after: [begin readCommentsError] + :end-before: [end readCommentsError] ``readComments()`` must copy the attribute's value before it returns; for example, like this: -.. code-block:: +.. literalinclude:: src/readHeader.cpp + :language: c++ :linenos: - - void - readComments (const char fileName[], string &comments) - { - RgbaInputFile file (fileName); - - comments = file.header().typedAttribute("comments").value(); - } + :start-after: [begin readComments] + :end-before: [end readComments] Luminance/Chroma and Gray-Scale Images -------------------------------------- @@ -509,6 +498,8 @@ pixels of each scan line are contiguous in memory. .. literalinclude:: src/writeGZ1.cpp :language: c++ :linenos: + :start-after: [begin writeGZ1] + :end-before: [end writeGZ1] On line 8, an OpenEXR header is created, and the header's display window and data window are both set to ``(0, 0) - (width-1, @@ -541,17 +532,19 @@ explicitly take the size of the pixels into account. With the values specified in our example, the OpenEXR library computes the address of the G channel of pixel ``(x,y)`` like this: -.. code-block:: - - (half*)((char*)gPixels + x * sizeof(half) * 1 + y * sizeof(half) * width) - = (half*)((char*)gPixels + x * 2 + y * 2 * width), +.. literalinclude:: src/writeGZ1.cpp + :language: c++ + :linenos: + :start-after: [begin compteChannelG] + :end-before: [end compteChannelG] The address of the Z channel of pixel ``(x,y)`` is -.. code-block:: - - (float*)((char*)zPixels + x * sizeof(float) * 1 + y * sizeof(float) * width) - = (float*)((char*)zPixels + x * 4 + y * 4 * width). +.. literalinclude:: src/writeGZ1.cpp + :language: c++ + :linenos: + :start-after: [begin compteChannelZ] + :end-before: [end compteChannelZ] The ``writePixels()`` call in line 29 copies the image's pixels from memory into the file. As in the RGBA-only interface, the argument to @@ -667,13 +660,12 @@ file, but instead of storing each image channel in a separate memory buffer, we interleave the channels in a single buffer. The buffer is an array of structs, which are defined like this: -.. code-block:: - - struct GZ - { - half g; - float z; - }; +.. literalinclude:: src/structDefinitions.cpp + :language: c++ + :linenos: + :dedent: + :start-after: [GZ definition begin] + :end-before: [GZ definition end] The code to read the file is almost the same as before; aside from reading only two instead of three channels, the only difference is how @@ -699,28 +691,22 @@ The file's header contains the file's channel list. Using iterators similar to those in the C++ Standard Template Library, we can iterate over the channels: -.. code-block:: +.. literalinclude:: src/readChannelsAndLayers.cpp + :language: c++ :linenos: - - const ChannelList &channels = file.header().channels(); - - for (ChannelList::ConstIterator i = channels.begin(); i != channels.end(); ++i) - { - const Channel &channel = i.channel(); - // ... - } + :dedent: + :start-after: [begin useIterator] + :end-before: [end useIterator] Channels can also be accessed by name, either with the ``[]`` operator, or with the f ``indChannel()`` function: -.. code-block:: +.. literalinclude:: src/readChannelsAndLayers.cpp + :language: c++ :linenos: - - const ChannelList &channels = file.header().channels(); - - const Channel &channel = channelList["G"]; - - const Channel *channelPtr = channelList.findChannel("G"); + :dedent: + :start-after: [begin directAccess] + :end-before: [end directAccess] The difference between the ``[]`` operator and ``findChannel()`` function is how errors are handled. If the channel in question is not present, @@ -758,27 +744,12 @@ corresponding layer. The following sample code prints the layers in a ``ChannelList`` and the channels in each layer: -.. code-block:: +.. literalinclude:: src/readChannelsAndLayers.cpp + :language: c++ :linenos: - - const ChannelList &channels = ... ; - - set layerNames; - - channels.layers (layerNames); - - for (set::const_iterator i = layerNames.begin(); i != layerNames.end(); ++i) - { - cout << "layer " << *i << endl; - - ChannelList::ConstIterator layerBegin, layerEnd; - channels.channelsInLayer (*i, layerBegin, layerEnd); - for (ChannelList::ConstIterator j = layerBegin; j != layerEnd; ++j) - { - cout << "tchannel " << j.name() << endl; - - } - } + :dedent: + :start-after: [begin layers] + :end-before: [end layers] Tiles, Levels and Level Modes ============================= @@ -826,32 +797,10 @@ An OpenEXR file's level mode and rounding mode, and the size of the tiles are stored in an attribute in the file header. The value of this attribute is a ``TileDescription`` object: -.. code-block:: +.. literalinclude:: src/tileDescription.cpp + :language: c++ :linenos: - enum LevelMode - { - ONE_LEVEL, - MIPMAP_LEVELS, - RIPMAP_LEVELS - }; - - enum LevelRoundingMode - { - ROUND_DOWN, - ROUND_UP - }; - - class TileDescription - { - public: - unsigned int xSize; // size of a tile in the x dimension - unsigned int ySize; // size of a tile in the y dimension - LevelMode mode; - LevelRoundingMode roundingMode; - ... // (methods omitted) - }; - Using the RGBA-only Interface for Tiled Files ============================================= @@ -889,9 +838,11 @@ tile coordinates ``(dx,dy)``, where ``dxMin`` ≤ ``dx`` ≤ ``dxMax`` and number of tiles in the x direction, and similarly, the ``numYTiles()`` method returns the number of tiles in the y direction. Thus, -.. code-block:: - - out.writeTiles (0, out.numXTiles() - 1, 0, out.numYTiles() - 1); +.. literalinclude:: src/writeTiledRgbaONE1.cpp + :language: c++ + :dedent: + :start-at: writeTiles + :end-at: writeTiles writes the entire image. @@ -934,6 +885,7 @@ frame buffer large enough for the highest-resolution level, ``(0,0)``, and reuse it for all levels: .. literalinclude:: src/writeTiledRgbaMIP1.cpp + :language: c++ :linenos: The main difference here is the use of ``MIPMAP_LEVELS`` on line 6 for @@ -1011,6 +963,7 @@ With a frame buffer that is large enough to hold level ``(0,0)``, we can write a ripmap file like this: .. literalinclude:: src/writeTiledRgbaRIP1.cpp + :language: c++ :linenos: As for ``ONE_LEVEL`` and ``MIPMAP_LEVELS`` files, the frame buffer @@ -1075,6 +1028,9 @@ tiles we want to read. .. literalinclude:: src/readTiled1.cpp :language: c++ :linenos: + :dedent: + :start-after: [begin readTiled1] + :end-before: [end readTiled1] In this example we assume that the file we want to read contains two channels, G and Z, of type ``HALF`` and ``FLOAT`` respectively. If the @@ -1084,15 +1040,19 @@ levels (``MIPMAP_LEVELS`` or ``MIPMAP_LEVELS``), we can access the extra levels by calling a four-argument version of the ``readTile()`` function: -.. code-block:: - - in.readTile (tileX, tileY, levelX, levelY); +.. literalinclude:: src/readTiled1.cpp + :language: c++ + :dedent: + :start-after: [begin v1] + :end-before: [end v1] or by calling a six-argument version of ``readTiles()``: -.. code-block:: - - in.readTiles (tileXMin, tileXMax, tileYMin, tileYMax, levelX, levelY); +.. literalinclude:: src/readTiled1.cpp + :language: c++ + :dedent: + :start-after: [end v1] + :end-before: [end v2] Deep Data Files =============== @@ -1119,9 +1079,11 @@ accepts ``DeepSlice`` as its input, except that it accepts ``Slice`` for sample count slice. The first difference we see from the previous version is: -.. code-block:: - - header.setType(DEEPSCANLINE); +.. literalinclude:: src/writeDeepScanLineFile.cpp + :language: c++ + :dedent: + :start-at: header.setType + :end-at: header.setType where we set the type of the header to a predefine string ``DEEPSCANLINE``, then we insert a sample count slice using @@ -1167,9 +1129,11 @@ The main the difference is we use the sample count slice and deep data slices. To do this, we added a new method to read the sample count table from the file: -.. code-block:: - - file.readPixelSampleCounts(dataWindow.min.y, dataWindow.max.y); +.. literalinclude:: src/readDeepScanLineFile.cpp + :language: c++ + :dedent: + :start-after: file.setFrameBuffer + :end-at: file.readPixelSampleCounts This method reads all pixel sample counts in the range ``[dataWindow.min.y, dataWindow.max.y]``, and stores the data to sample @@ -1215,6 +1179,7 @@ An example of reading a deep tiled file created by code explained in the `Writing a Deep Tiled File`_ section. .. literalinclude:: src/readDeepTiledFile.cpp + :language: c++ :linenos: This code demonstrates how to read the first level of a deep tiled @@ -1313,28 +1278,28 @@ busy, and we want to split the processors evenly between input and output. Before creating the input and output threads, the application instructs the OpenEXR library to create four worker threads: -.. code-block:: - - // main, before application threads are created: - setGlobalThreadCount (4); +.. literalinclude:: src/multithreading.cpp + :language: c++ + :dedent: + :start-after: [begin main thread create] + :end-before: [begin applications input thread] In the input and output threads, input and output files are opened with ``numThreads`` set to 2: -.. code-block:: - - // application's input thread - - InputFile in (fileName, 2); - - ... - - // application's output thread +.. literalinclude:: src/multithreading.cpp + :language: c++ + :dedent: + :start-after: [begin applications input thread] + :end-before: [end applications input thread] - OutputFile out (fileName, header, 2); +.. literalinclude:: src/multithreading.cpp + :language: c++ + :dedent: + :start-after: [begin applications output thread] + :end-before: [end applications output thread] - ... This ensures that file input and output in the application's two threads can proceed concurrently, without one thread stalling the @@ -1388,12 +1353,14 @@ this, we derive a new class, ``C_IStream``, from ``IStream``. The declaration of class ``IStream`` looks like this: .. literalinclude:: src/IStream.cpp + :language: c++ :linenos: Our derived class needs a public constructor, and it must override four methods: .. literalinclude:: src/C_IStream.cpp + :language: c++ :linenos: ``read(c,n)`` reads ``n`` bytes from the file, and stores them in @@ -1403,6 +1370,7 @@ exception. If ``read(c,n)`` hits the end of the file after reading ``n`` bytes, it returns ``false``, otherwise it returns ``true``: .. literalinclude:: src/C_IStream_read.cpp + :language: c++ :linenos: ``tellg()`` returns the current reading position, in bytes, from the @@ -1410,18 +1378,21 @@ beginning of the file. The next ``read()`` call will begin reading at the indicated position: .. literalinclude:: src/C_IStream_tellg.cpp + :language: c++ :linenos: ``seekg(pos)`` sets the current reading position to ``pos`` bytes from the beginning of the file: .. literalinclude:: src/C_IStream_seekg.cpp + :language: c++ :linenos: ``clear()`` clears any error flags that may be set on the file after a ``read()`` or ``seekg()`` operation has failed: .. literalinclude:: src/C_IStream_clear.cpp + :language: c++ :linenos: In order to read an RGBA image from an open C stdio file, we first @@ -1462,6 +1433,7 @@ functions, ``isMemoryMapped()`` and ``readMemoryMapped()``, in addition to the functions needed for regular, non-memory-mapped input: .. literalinclude:: src/MemoryMappedIStream.cpp + :language: c++ :linenos: The constructor for class ``MemoryMappedIStream`` maps the contents of @@ -1471,6 +1443,7 @@ POSIX ``mmap()`` system call. On Windows files can be memory-mapped by calling ``CreateFileMapping()`` and ``MapViewOfFile()``: .. literalinclude:: src/MemoryMappedIStream_constructor.cpp + :language: c++ :linenos: The destructor frees the address range associated with the file by @@ -1479,6 +1452,7 @@ Windows version would call ``UnmapViewOfFile()`` and ``CloseHandle()``: .. literalinclude:: src/MemoryMappedIStream_destructor.cpp + :language: c++ :linenos: Function ``isMemoryMapped()`` returns ``true`` to indicate that @@ -1486,6 +1460,7 @@ memory-mapped input is supported. This allows the OpenEXR library to call ``readMemoryMapped()`` instead of ``read()``: .. literalinclude:: src/MemoryMappedIStream_isMemoryMapped.cpp + :language: c++ :linenos: ``readMemoryMapped()`` is analogous to ``read()``, but instead of @@ -1494,12 +1469,14 @@ copying data into a buffer supplied by the caller, thus avoiding the copy operation: .. literalinclude:: src/MemoryMappedIStream_readMemoryMapped.cpp + :language: c++ :linenos: The ``MemoryMappedIStream`` class must also implement the regular ``read()`` function, as well as ``tellg()`` and ``seekg()``: .. literalinclude:: src/MemoryMappedIStream_read.cpp + :language: c++ :linenos: Class ``MemoryMappedIStream`` does not need a ``clear()`` @@ -1541,19 +1518,11 @@ and ``0x01``. Given a file name, the following function returns ``true`` if the corresponding file exists, is readable, and contains an OpenEXR image: -.. code-block:: +.. literalinclude:: src/validExrFile.cpp + :language: c++ :linenos: - - bool - isThisAnOpenExrFile (const char fileName[]) - { - std::ifstream f (fileName, std::ios_base::binary); - - char b[4]; - f.read (b, sizeof (b)); - - return !!f && b[0] == 0x76 && b[1] == 0x2f && b[2] == 0x31 && b[3] == 0x01; - } + :start-after: [begin validFileCheck] + :end-before: [end validFileCheck] Using this function does not require linking with the OpenEXR library. @@ -1561,20 +1530,11 @@ Programs that are linked with the OpenEXR library can determine if a given file is an OpenEXR file by calling one of the following functions, which are part of the library: -.. code-block:: +.. literalinclude:: src/validExrFile.cpp + :language: c++ :linenos: - - bool isOpenExrFile (const char fileName[], bool &isTiled); - - bool isOpenExrFile (const char fileName[]); - - bool isTiledOpenExrFile (const char fileName[]); - - bool isOpenExrFile (IStream &is, bool &isTiled); - - bool isOpenExrFile (IStream &is); - - bool isTiledOpenExrFile (IStream &is); + :start-after: [begin otherValidFileChecks] + :end-before: [end otherValidFileChecks] Is this File Complete? ---------------------- @@ -1590,15 +1550,11 @@ faster and more convenient. The following function returns ``true`` or ``false``, depending on whether a given OpenEXR file is complete or not: -.. code-block:: +.. literalinclude:: src/validExrFile.cpp + :language: c++ :linenos: - - bool - isComplete (const char fileName[]) - { - InputFile in (fileName); - return in.isComplete(); - } + :start-after: [begin completeFileCheck] + :end-before: [end completeFileCheck] Preview Images -------------- @@ -1622,27 +1578,12 @@ correction or tone mapping is required.) The code fragment below shows how to test if an OpenEXR file has a preview image, and how to access a preview image's pixels: -.. code-block:: +.. literalinclude:: src/previewImageExamples.cpp + :language: c++ + :dedent: :linenos: - - RgbaInputFile file (fileName); - - if (file.header().hasPreviewImage()) - { - const PreviewImage &preview = file.header().previewImage(); - - for (int y = 0; y < preview.height(); ++y) - { - for (int x = 0; x < preview.width(); ++x) - { - - const PreviewRgba &pixel = preview.pixel (x, y); - - ... - - } - } - } + :start-after: [begin accessPreviewImage] + :end-before: [end accessPreviewImage] Writing an OpenEXR file with a preview image is shown in the following example. Since the preview image is an attribute in the file's header, @@ -1667,9 +1608,12 @@ Function ``makePreviewImage()``, called on line 12, generates the preview image by scaling the main image down to one eighth of its original width and height: -.. literalinclude:: src/makePreviewImage.cpp +.. literalinclude:: src/previewImageExamples.cpp :language: c++ :linenos: + :dedent: + :start-after: [begin makePreviewImage] + :end-before: [end makePreviewImage] To make this example easier to read, scaling the image is done by just sampling every eighth pixel of every eighth scan line. This can lead @@ -1683,8 +1627,12 @@ image pixels to ``unsigned char`` values. ``gamma()`` is a simplified version of what a program should do on order to show an OpenEXR image's floating-point pixels on the screen: -.. literalinclude:: src/gamma.cpp +.. literalinclude:: src/previewImageExamples.cpp + :language: c++ :linenos: + :dedent: + :start-after: [begin gamma] + :end-before: [end gamma] ``makePreviewImage()`` converts the pixels' alpha component to unsigned char by by linearly mapping the range ``[0.0, 1.0]`` to @@ -1773,16 +1721,12 @@ faces. The following code fragment tests if an OpenEXR file contains an environment map, and if it does, which kind: -.. code-block:: +.. literalinclude:: src/envmap.cpp + :language: c++ :linenos: - - RgbaInputFile file (fileName); - - if (hasEnvmap (file.header())) - { - Envmap type = envmap (file.header()); - ... - } + :dedent: + :start-after: [begin hasEnvmap] + :end-before: [end hasEnvmap] For each kind of environment map, the OpenEXR library provides a set of routines that convert from 3D directions to 2D floating-point pixel @@ -1799,13 +1743,11 @@ compression algorithms. To specify the compression algorithm, set the ``compression()`` value on the ``Header`` object: -.. code-block:: - :linenos: - - Header header (width, height); - header.channels().insert ("G", Channel (HALF)); - header.channels().insert ("Z", Channel (FLOAT)); - header.compression() = ZIP_COMPRESSION; +.. literalinclude:: src/compression.cpp + :language: c++ + :dedent: + :start-after: [begin setCompression] + :end-before: zipCompressionLevel Supported compression types are: @@ -1842,25 +1784,19 @@ user-controllable compression level, which determines the space/time tradeoff. You can control these levels either by setting a global default or by setting the level directly on the ``Header`` object. -.. code-block:: - - setDefaultZipCompressionLevel (6); - setDefaultDwaCompressionLevel (45.0f); +.. literalinclude:: src/compression.cpp + :language: c++ + :dedent: + :start-after: [begin setCompressionDefault] + :end-before: [end setCompressionDefault] The default zip compression level is 4 for OpenEXR v3.1.3+ and 6 for previous versions. The default DWA compression level is 45.0f. Alternatively, set the compression level on the ``Header`` object: -.. code-block:: - :linenos: - - Header header (width, height); - header.channels().insert ("G", Channel (HALF)); - header.channels().insert ("Z", Channel (FLOAT)); - header.compression() = ZIP_COMPRESSION; - header.zipCompressionLevel() = 6; - - - - +.. literalinclude:: src/compression.cpp + :language: c++ + :dedent: + :start-after: [begin setCompression] + :end-before: [end setCompression] \ No newline at end of file diff --git a/website/TechnicalIntroduction.rst b/website/TechnicalIntroduction.rst index 92ebf748ac..dfd6623626 100644 --- a/website/TechnicalIntroduction.rst +++ b/website/TechnicalIntroduction.rst @@ -390,7 +390,7 @@ Examples: level contains 4 by 4 pixels have the following level numbers: +------------+-------+------------------------+ -| | | .. centered: **width** | +| | | .. centered::**width** | +------------+-------+-------+-------+--------+ | | | **4** | **2** | **1** | +------------+-------+-------+-------+--------+ diff --git a/website/bin/exrmanifest.rst b/website/bin/exrmanifest.rst new file mode 100644 index 0000000000..8eb6f603b0 --- /dev/null +++ b/website/bin/exrmanifest.rst @@ -0,0 +1,33 @@ +.. + SPDX-License-Identifier: BSD-3-Clause + Copyright Contributors to the OpenEXR Project. + +exrmanifest +########### + +:: + + exrmanifest [options] imagefile [imagefile ...] + + +Description +----------- + +Read exr files and print the contents of the embedded manifest. + +Options: +-------- + +.. describe:: -v, --verbose + + verbose mode + +.. describe:: -h, --help + + print this message + +.. describe:: --version + + print version information + +Report bugs via https://github.com/AcademySoftwareFoundation/openexr/issues or email security@openexr.com diff --git a/website/concepts.rst b/website/concepts.rst index e1dbc397d0..aadcf858fc 100644 --- a/website/concepts.rst +++ b/website/concepts.rst @@ -16,6 +16,7 @@ OpenEXR Concepts MultiViewOpenEXR InterpretingDeepPixels TheoryDeepPixels + DeepIDsSpecification OpenEXRFileLayout PortingGuide SymbolVisibility diff --git a/website/conf.py b/website/conf.py index d8b3e5ff51..389c9ff8b9 100644 --- a/website/conf.py +++ b/website/conf.py @@ -147,7 +147,7 @@ html_theme = "press" html_theme_options = { "external_links": [ - ("Github", "https://github.com/AcademySoftwareFoundation/openexr"), + ("GitHub", "https://github.com/AcademySoftwareFoundation/openexr"), ] } diff --git a/website/downloads/sample.exr b/website/downloads/sample.exr new file mode 100644 index 0000000000..ddafd1a854 Binary files /dev/null and b/website/downloads/sample.exr differ diff --git a/website/index.rst b/website/index.rst index cbf701c0d8..2c4ddf9daa 100644 --- a/website/index.rst +++ b/website/index.rst @@ -34,12 +34,12 @@ OpenEXR is a project of the `Academy Software Foundation Latest News =========== -.. highlights:: +.. image:: images/news.png + :width: 50 + :height: 50 + :align: left - .. image:: images/news.png - :width: 100 - :height: 100 - :align: left +.. highlights:: .. include:: news.rst :start-after: .. _LatestNewsStart: @@ -82,6 +82,8 @@ Community - Calendar: https://lists.aswf.io/g/openexr-dev/calendar + - Meeting Notes: https://wiki.aswf.io/display/OEXR/TSC+Meetings + * **Report a bug:** - Submit an Issue: https://github.com/AcademySoftwareFoundation/openexr/issues diff --git a/website/install.rst b/website/install.rst index 8a0d265861..318b2a869c 100644 --- a/website/install.rst +++ b/website/install.rst @@ -10,6 +10,9 @@ Install .. toctree:: :caption: Install +Linux +----- + The OpenEXR library is available for download and installation in binary form via package managers on many Linux distributions. See `https://pkgs.org/download/openexr @@ -33,21 +36,37 @@ Beware that some distributions are out of date and only provide distributions of outdated releases OpenEXR. We recommend against using OpenEXR v2, and we *strongly* recommend against using OpenEXR v1. +Refer to the current version of OpenEXR on various major Linux distros at +`repology.org `_: + +.. image:: https://repology.org/badge/vertical-allrepos/openexr.svg?exclude_unsupported=1&columns=4 + :target: https://repology.org/project/openexr/versions + +macOS +----- + On macOS, install via `Homebrew `_: .. code-block:: % brew install openexr -We do not recommend installation via -`Macports `_ because the -distribution is out of date. +Alternatively, you can install on macOS via `MacPorts +`_: + +.. code-block:: + + % port install openexr + +Windows +------- + +Install via `vcpkg `_: + +.. code-block:: + + % .\vcpkg install openexr -Also note that the official OpenEXR project does not provide supported -python bindings. ``pip install openexr`` installs the `openexrpython -`_ module, which is not -affiliated with the OpenEXR project or the ASWF. Please direct -questions there. Build from Source ----------------- @@ -68,7 +87,7 @@ Prerequisites Make sure these are installed on your system before building OpenEXR: -* OpenEXR requires CMake version 3.12 or newer +* OpenEXR requires CMake version 3.14 or newer * C++ compiler that supports C++11 * Imath (auto fetched by CMake if not found) (https://github.com/AcademySoftwareFoundation/openexr) * libdeflate source code (auto fetched by CMake if not found) (https://github.com/ebiggers/libdeflate) @@ -264,6 +283,29 @@ You can customize these options three ways: 2. Use the UI ``cmake-gui`` or ``ccmake``. 3. Specify them as command-line arguments when you invoke cmake. +Uninstall +~~~~~~~~~ + +If you did a binary instal of OpenEXR via a package manager +(`apt-get`, `yum`, `port`, `brew`, etc), use the package manager to +uninstall. + +If you have installed from source, *and you still have the build +tree from which you installed*, you can uninstall via: + +.. code-block:: + + % cmake --build $builddir --target uninstall + +or if using ``make``: + +.. code-block:: + + % make uninstall + +The `uninstall` relies on CMake's `install_manifest.txt` for the record +of what was installed. + Library Naming Options ~~~~~~~~~~~~~~~~~~~~~~ @@ -410,12 +452,17 @@ Component Options * ``OPENEXR_BUILD_TOOLS`` - Build and install the binary programs (exrheader, exrinfo, + Build the binary programs (exrheader, exrinfo, + exrmakepreview, etc). Default is ``ON``. + +* ``OPENEXR_INSTALL_TOOLS`` + + Install the binary programs (exrheader, exrinfo, exrmakepreview, etc). Default is ``ON``. -* ``OPENEXR_INSTALL_EXAMPLES`` +* ``OPENEXR_BUILD_EXAMPLES`` - Build and install the example code. Default is ``ON``. + Build the example code. Default is ``ON``. Additional CMake Options ~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/website/news.rst b/website/news.rst index 86cac87927..67fc498c4c 100644 --- a/website/news.rst +++ b/website/news.rst @@ -13,11 +13,95 @@ News .. toctree:: :caption: News -September 20, 2023 - ASWF Dev Days -================================== +March 6, 2024 - OpenEXR v3.2.3 Released +======================================= .. _LatestNewsStart: +OpenEXR v3.2.3 is released and available for download from `v3.2.3 +`_. + +Patch release with various build/bug/documentation fixes: + +* Fix ``bswap`` on NetBSD +* Fix issue with decompressing fp32 dwa files +* Support cmake config for ``libdeflate`` +* updated security policy +* miscelleneous website improvements + +This release also addresses several recent fuzz/security issues, including: + +* OSS-fuzz `66676 + `_ Null-dereference in Imf_3_3::realloc_deepdata + +* OSS-fuzz `66612 + `_ Null-dereference in Imf_3_3::realloc_deepdata + +This release also formally adopts the process of publishing openexr +python wheels to `pypi.org `_. + +This release also introduces the process of signing release artifacts +via `sigstore `_. + +.. _LatestNewsEnd: + +February 28, 2024 - Imath v3.1.11 Released +========================================== + +Imath v3.1.11 is released and available for download from `v3.1.11 +`_. + +Patch release with small build fix: + +* Add explicit ``std::`` namespace for ``isfinite`` in ``ImathFun.cpp`` + +This release also introduces the practice of signing release artifacts +via `sigstore `_. + +February 11, 2024 - OpenEXR v3.2.2 Released +=========================================== + +OpenEXR v3.2.2 is released and available for download from `v3.2.2 +`_. + +Patch release that addresses +`CVE-2023-5841 `_. + +February 11, 2024 - OpenEXR v3.1.12 Released +============================================ + +OpenEXR v3.1.12 is released and available for download from `v3.1.12 +`_. + +Patch release that addresses +`CVE-2023-5841 `_. + +December 19, 2023 - OpenEXR v2.5.10 Released +============================================ + +OpenEXR v2.5.10 is released and available for download from `v2.5.10 +`_. + +Patch release for OpenEXR v2.5 that fixes a build failure on macOS +prior to 10.6 (fallback for missing `libdispatch`). + +September 23, 2024 - OpenEXR v3.2.1 Released +============================================ + +OpenEXR v3.2.1 is released and available for download from `v3.2.1 +`_. + +Patch release with miscellaneous build fixes: + +* Fix for linking statically against an external ``libdeflate`` +* Fix a compile error with ``OPENEXR_VERSION_HEX`` +* Fix various compiler warnings +* Pkg-config generation is now on by default for all systems, including Windows + + +September 20, 2023 - ASWF Dev Days +================================== + OpenEXR is participating in the Academy Software Foundation's `Dev Days `_, a great way to learn about how to contribute to open source software. Project @@ -50,8 +134,6 @@ A list of ideas for more substantial contributions is on the `OpenEXR wiki these are beyond the scope of the spirit of Dev Days, feel free to discuss these as well. -.. _LatestNewsEnd: - August 30, 2023 - OpenEXR v3.2.0 Released ========================================= diff --git a/website/requirements.txt b/website/requirements.txt index d030b3b5db..52636e9869 100644 --- a/website/requirements.txt +++ b/website/requirements.txt @@ -1,3 +1,3 @@ -sphinx == 4.4.0 +sphinx >= 5.0 breathe sphinx-press-theme diff --git a/website/src/all.cpp b/website/src/all.cpp index 4880887777..1835a23a28 100644 --- a/website/src/all.cpp +++ b/website/src/all.cpp @@ -81,8 +81,6 @@ namespace XXX { #include "C_IStream_read.cpp" #include "C_IStream_seekg.cpp" #include "C_IStream_tellg.cpp" -#include "gamma.cpp" -#include "makePreviewImage.cpp" #ifndef _WIN32 #include "MemoryMappedIStream.cpp" #include "MemoryMappedIStream_isMemoryMapped.cpp" @@ -109,6 +107,31 @@ namespace XXX { #include "writeGZ2.cpp" #include "writeRgba1.cpp" #include "writeRgba2.cpp" +#include "readChannelsAndLayers.cpp" +#include "tileDescription.cpp" +#include "validExrFile.cpp" +#include "previewImageExamples.cpp" + + +void structDefinitions() +{ + #include "structDefinitions.cpp" +} + +void multithreading() +{ + #include "multithreading.cpp" +} + +void envmap() +{ + #include "envmap.cpp" +} + +void compression() +{ + #include "compression.cpp" +} int main(int argc, char* argv[]) diff --git a/website/src/compression.cpp b/website/src/compression.cpp new file mode 100644 index 0000000000..dd0d60913f --- /dev/null +++ b/website/src/compression.cpp @@ -0,0 +1,14 @@ + +int width=1; int height=1; +// [begin setCompression] +Header header (width, height); +header.channels().insert ("G", Channel (HALF)); +header.channels().insert ("Z", Channel (FLOAT)); +header.compression() = ZIP_COMPRESSION; +header.zipCompressionLevel() = 6; +// [end setCompression] + +// [begin setCompressionDefault] +setDefaultZipCompressionLevel (6); +setDefaultDwaCompressionLevel (45.0f); +// [end setCompressionDefault] \ No newline at end of file diff --git a/website/src/envmap.cpp b/website/src/envmap.cpp new file mode 100644 index 0000000000..e2df19c21f --- /dev/null +++ b/website/src/envmap.cpp @@ -0,0 +1,11 @@ + +char fileName[] = ""; +// [begin hasEnvmap] +RgbaInputFile file (fileName); + +if (hasEnvmap (file.header())) +{ + Envmap type = envmap (file.header()); + // ... +} +// [end hasEnvmap] diff --git a/website/src/reader/CMakeLists.txt b/website/src/exrreader/CMakeLists.txt similarity index 75% rename from website/src/reader/CMakeLists.txt rename to website/src/exrreader/CMakeLists.txt index f33f3df026..17356928fe 100644 --- a/website/src/reader/CMakeLists.txt +++ b/website/src/exrreader/CMakeLists.txt @@ -2,6 +2,6 @@ cmake_minimum_required(VERSION 3.12) project(exrreader) find_package(OpenEXR REQUIRED) -add_executable(${PROJECT_NAME} reader.cpp) +add_executable(${PROJECT_NAME} exrreader.cpp) target_link_libraries(${PROJECT_NAME} OpenEXR::OpenEXR) diff --git a/website/src/exrreader/build.sh b/website/src/exrreader/build.sh new file mode 100755 index 0000000000..a93f250f6c --- /dev/null +++ b/website/src/exrreader/build.sh @@ -0,0 +1,3 @@ +$ mkdir _build +$ cmake -S . -B _build -DCMAKE_PREFIX_PATH= +$ cmake --build _build diff --git a/website/src/reader/reader.cpp b/website/src/exrreader/exrreader.cpp similarity index 100% rename from website/src/reader/reader.cpp rename to website/src/exrreader/exrreader.cpp diff --git a/website/src/writer/CMakeLists.txt b/website/src/exrwriter/CMakeLists.txt similarity index 75% rename from website/src/writer/CMakeLists.txt rename to website/src/exrwriter/CMakeLists.txt index 79052a6ac7..ceefb2b4f0 100644 --- a/website/src/writer/CMakeLists.txt +++ b/website/src/exrwriter/CMakeLists.txt @@ -2,6 +2,6 @@ cmake_minimum_required(VERSION 3.12) project(exrwriter) find_package(OpenEXR REQUIRED) -add_executable(${PROJECT_NAME} writer.cpp) +add_executable(${PROJECT_NAME} exrwriter.cpp) target_link_libraries(${PROJECT_NAME} OpenEXR::OpenEXR) diff --git a/website/src/exrwriter/build.sh b/website/src/exrwriter/build.sh new file mode 100755 index 0000000000..a93f250f6c --- /dev/null +++ b/website/src/exrwriter/build.sh @@ -0,0 +1,3 @@ +$ mkdir _build +$ cmake -S . -B _build -DCMAKE_PREFIX_PATH= +$ cmake --build _build diff --git a/website/src/writer/writer.cpp b/website/src/exrwriter/exrwriter.cpp similarity index 100% rename from website/src/writer/writer.cpp rename to website/src/exrwriter/exrwriter.cpp diff --git a/website/src/gamma.cpp b/website/src/gamma.cpp deleted file mode 100644 index a5695d26d9..0000000000 --- a/website/src/gamma.cpp +++ /dev/null @@ -1,7 +0,0 @@ -unsigned char -gamma (float x) -{ - x = pow (5.5555f * max (0.f, x), 0.4545f) * 84.66f; - return (unsigned char) clamp (x, 0.f, 255.f); -} - diff --git a/website/src/makePreviewImage.cpp b/website/src/makePreviewImage.cpp deleted file mode 100644 index 794b3b0df8..0000000000 --- a/website/src/makePreviewImage.cpp +++ /dev/null @@ -1,31 +0,0 @@ -void -makePreviewImage ( - const Array2D& pixels, - int width, - int height, - Array2D& previewPixels, - int& previewWidth, - int& previewHeight) -{ - const int N = 8; - - previewWidth = width / N; - previewHeight = height / N; - - previewPixels.resizeErase (previewHeight, previewWidth); - - for (int y = 0; y < previewHeight; ++y) - { - for (int x = 0; x < previewWidth; ++x) - { - - const Rgba& inPixel = pixels[y * N][x * N]; - PreviewRgba& outPixel = previewPixels[y][x]; - - outPixel.r = gamma (inPixel.r); - outPixel.g = gamma (inPixel.g); - outPixel.b = gamma (inPixel.b); - outPixel.a = static_cast (clamp (inPixel.a * 255.f, 0.f, 255.f) + 0.5f); - } - } -} diff --git a/website/src/multithreading.cpp b/website/src/multithreading.cpp new file mode 100644 index 0000000000..a4bd345ae3 --- /dev/null +++ b/website/src/multithreading.cpp @@ -0,0 +1,22 @@ +char fileName[] = ""; +// [begin main thread create] +// main, before application threads are created: + +setGlobalThreadCount (4); +// [begin applications input thread] +// application's input thread + +InputFile in (fileName); + +// ... +// [end applications input thread] + +Header header = in.header(); +// [begin applications output thread] +// application's output thread + +OutputFile out (fileName, header, 2); + +// ... +// [end applications output thread] + diff --git a/website/src/previewImageExamples.cpp b/website/src/previewImageExamples.cpp new file mode 100644 index 0000000000..125cd22213 --- /dev/null +++ b/website/src/previewImageExamples.cpp @@ -0,0 +1,67 @@ +void +accessPreviewImage (const char fileName[]) +{ + // [begin accessPreviewImage] + RgbaInputFile file (fileName); + + if (file.header().hasPreviewImage()) + { + const PreviewImage &preview = file.header().previewImage(); + + for (int y = 0; y < preview.height(); ++y) + { + for (int x = 0; x < preview.width(); ++x) + { + + const PreviewRgba &pixel = preview.pixel (x, y); + + // ... + + } + } + } + // [end accessPreviewImage] +} + +// [begin gamma] +unsigned char +gamma (float x) +{ + x = pow (5.5555f * max (0.f, x), 0.4545f) * 84.66f; + return (unsigned char) Imath::clamp (x, 0.f, 255.f); +} +// [end gamma] + +// [begin makePreviewImage] +void +makePreviewImage ( + const Array2D& pixels, + int width, + int height, + Array2D& previewPixels, + int& previewWidth, + int& previewHeight) +{ + const int N = 8; + + previewWidth = width / N; + previewHeight = height / N; + + previewPixels.resizeErase (previewHeight, previewWidth); + + for (int y = 0; y < previewHeight; ++y) + { + for (int x = 0; x < previewWidth; ++x) + { + + const Rgba& inPixel = pixels[y * N][x * N]; + PreviewRgba& outPixel = previewPixels[y][x]; + + outPixel.r = gamma (inPixel.r); + outPixel.g = gamma (inPixel.g); + outPixel.b = gamma (inPixel.b); + outPixel.a = static_cast (Imath::clamp (inPixel.a * 255.f, 0.f, 255.f) + 0.5f); + } + } +} +// [end makePreviewImage] diff --git a/website/src/readChannelsAndLayers.cpp b/website/src/readChannelsAndLayers.cpp new file mode 100644 index 0000000000..79a3b1c90e --- /dev/null +++ b/website/src/readChannelsAndLayers.cpp @@ -0,0 +1,53 @@ +void +readChannels(const char fileName[]) +{ + InputFile file (fileName); + + // [begin useIterator] + const ChannelList &channels = file.header().channels(); + + for (ChannelList::ConstIterator i = channels.begin(); i != channels.end(); ++i) + { + const Channel &channel = i.channel(); + // ... + } + // [end useIterator] + + // [begin directAccess] + // const ChannelList &channels = file.header().channels(); + + const Channel &channel = channels["G"]; + + const Channel *channelPtr = channels.findChannel("G"); + // [end directAccess] + +} + +void +readLayers (const char fileName[]) +{ + InputFile file (fileName); + + // [begin layers] + const ChannelList &channels = file.header().channels(); ; + + std::set layerNames; + + channels.layers (layerNames); + + for (std::set::const_iterator i = layerNames.begin(); i != layerNames.end(); ++i) + { + cout << "layer " << *i << endl; + + ChannelList::ConstIterator layerBegin, layerEnd; + channels.channelsInLayer (*i, layerBegin, layerEnd); + for (ChannelList::ConstIterator j = layerBegin; j != layerEnd; ++j) + { + cout << "tchannel " << j.name() << endl; + } + } + // [end layers] +} + + + diff --git a/website/src/readGZ1.cpp b/website/src/readGZ1.cpp index 89372790b4..dee8c9d16f 100644 --- a/website/src/readGZ1.cpp +++ b/website/src/readGZ1.cpp @@ -51,4 +51,4 @@ readGZ1 ( file.setFrameBuffer (frameBuffer); file.readPixels (dw.min.y, dw.max.y); -} +} \ No newline at end of file diff --git a/website/src/readHeader.cpp b/website/src/readHeader.cpp index 315cd27bcd..39b4f51275 100644 --- a/website/src/readHeader.cpp +++ b/website/src/readHeader.cpp @@ -1,3 +1,4 @@ +// [begin readHeader] void readHeader (const char fileName[]) { @@ -14,3 +15,26 @@ readHeader (const char fileName[]) if (cameraTransform) cout << "cameraTransformn" << cameraTransform->value () << flush; } +// [end readHeader] + +// [begin readComments] +void +readComments (const char fileName[], string &comments) +{ + RgbaInputFile file (fileName); + + comments = file.header().typedAttribute("comments").value(); +} +// [end readComments] + +// [begin readCommentsError] +void +readComments (const char fileName[], const StringAttribute *&comments) +{ + // error: comments pointer is invalid after this function returns + + RgbaInputFile file (fileName); + + comments = file.header().findTypedAttribute ("comments"); +} +// [end readCommentsError] diff --git a/website/src/readTiled1.cpp b/website/src/readTiled1.cpp index 7f386f10f3..e0943b543d 100644 --- a/website/src/readTiled1.cpp +++ b/website/src/readTiled1.cpp @@ -1,3 +1,4 @@ +// [begin readTiled1] void readTiled1 (const char fileName[], Array2D& pixels, int& width, int& height) { @@ -33,3 +34,16 @@ readTiled1 (const char fileName[], Array2D& pixels, int& width, int& height) in.setFrameBuffer (frameBuffer); in.readTiles (0, in.numXTiles () - 1, 0, in.numYTiles () - 1); } +// [end readTiled1] +void +readTiledOtherVersions (const char fileName[]) +{ + // read tile function versions + TiledInputFile in(fileName); + int tileX, tileY, levelX, levelY, tileXMin, tileXMax, tileYMin, tileYMax; + // [begin v1] + in.readTile (tileX, tileY, levelX, levelY); + // [end v1] + in.readTiles (tileXMin, tileXMax, tileYMin, tileYMax, levelX, levelY); + // [end v2] +} \ No newline at end of file diff --git a/website/src/reader/build.sh b/website/src/reader/build.sh deleted file mode 100755 index 7cd8c5801a..0000000000 --- a/website/src/reader/build.sh +++ /dev/null @@ -1,3 +0,0 @@ -$ mkdir _build -$ cmake -S . -B _build -$ cmake --build _build diff --git a/website/src/structDefinitions.cpp b/website/src/structDefinitions.cpp new file mode 100644 index 0000000000..2f44d9b718 --- /dev/null +++ b/website/src/structDefinitions.cpp @@ -0,0 +1,17 @@ +// [Rgba definition begin] +struct Rgba +{ + half r; // red + half g; // green + half b; // blue + half a; // alpha (opacity) +}; +// [Rgba definition end] + +// [GZ definition begin] +struct GZ +{ + half g; + float z; +}; +// [GZ definition end] \ No newline at end of file diff --git a/website/src/tileDescription.cpp b/website/src/tileDescription.cpp new file mode 100644 index 0000000000..2f1968d592 --- /dev/null +++ b/website/src/tileDescription.cpp @@ -0,0 +1,23 @@ +// Enums defined in ImfTileDescription.h +// enum LevelMode +// { +// ONE_LEVEL, +// MIPMAP_LEVELS, +// RIPMAP_LEVELS +// }; + +// enum LevelRoundingMode +// { +// ROUND_DOWN, +// ROUND_UP +// }; + +class TileDescription +{ + public: + unsigned int xSize; // size of a tile in the x dimension + unsigned int ySize; // size of a tile in the y dimension + LevelMode mode; + LevelRoundingMode roundingMode; + // ... (methods omitted) +}; \ No newline at end of file diff --git a/website/src/validExrFile.cpp b/website/src/validExrFile.cpp new file mode 100644 index 0000000000..99a4209b2e --- /dev/null +++ b/website/src/validExrFile.cpp @@ -0,0 +1,35 @@ +#include +// [begin validFileCheck] +bool +isThisAnOpenExrFile (const char fileName[]) +{ + std::ifstream f (fileName, std::ios_base::binary); + + char b[4]; + f.read (b, sizeof (b)); + + return !!f && b[0] == 0x76 && b[1] == 0x2f && b[2] == 0x31 && b[3] == 0x01; +} +// [end validFileCheck] +// [begin completeFileCheck] +bool +isComplete (const char fileName[]) +{ + InputFile in (fileName); + return in.isComplete(); +} +// [end completeFileCheck] + +// [begin otherValidFileChecks] +bool isOpenExrFile (const char fileName[], bool &isTiled); + +bool isOpenExrFile (const char fileName[]); + +bool isTiledOpenExrFile (const char fileName[]); + +bool isOpenExrFile (IStream &is, bool &isTiled); + +bool isOpenExrFile (IStream &is); + +bool isTiledOpenExrFile (IStream &is); +// [end otherValidFileChecks] \ No newline at end of file diff --git a/website/src/writeGZ1.cpp b/website/src/writeGZ1.cpp index 0f7fe86e08..0ab69df657 100644 --- a/website/src/writeGZ1.cpp +++ b/website/src/writeGZ1.cpp @@ -1,3 +1,4 @@ +// [begin writeGZ1] void writeGZ1 ( const char fileName[], @@ -33,3 +34,21 @@ writeGZ1 ( file.setFrameBuffer (frameBuffer); // 16 file.writePixels (height); // 17 } +// [end writeGZ1] + +// Computing address of G and Z channels +const half* gPixels; +const float* zPixels; +int x, y, width; + +half* G = +// [begin compteChannelG] +(half*)((char*)gPixels + x * sizeof(half) * 1 + y * sizeof(half) * width); + // = (half*)((char*)gPixels + x * 2 + y * 2 * width); +// [end compteChannelG] + +// [begin compteChannelZ] +float* Z = +(float*)((char*)zPixels + x * sizeof(float) * 1 + y * sizeof(float) * width); + // = (float*)((char*)zPixels + x * 4 + y * 4 * width); +// [end compteChannelZ] diff --git a/website/src/writeRgba1.cpp b/website/src/writeRgba1.cpp index 1245e19f3d..27a47cae11 100644 --- a/website/src/writeRgba1.cpp +++ b/website/src/writeRgba1.cpp @@ -1,3 +1,4 @@ +// [begin writeRgba1] void writeRgba1 (const char fileName[], const Rgba* pixels, int width, int height) { @@ -5,3 +6,18 @@ writeRgba1 (const char fileName[], const Rgba* pixels, int width, int height) file.setFrameBuffer (pixels, 1, width); // 2 file.writePixels (height); // 3 } +// [end writeRgba1] + +void +tryCatchExample (const char fileName[], const Rgba* pixels, int width, int height) { + // [begin tryCatchExample] + try + { + writeRgba1 (fileName, pixels, width, height); + } + catch (const std::exception &exc) + { + std::cerr << exc.what() << std::endl; + } + // [end tryCatchExample] +} \ No newline at end of file diff --git a/website/src/writeRgba2.cpp b/website/src/writeRgba2.cpp index af5f71d353..87b0011610 100644 --- a/website/src/writeRgba2.cpp +++ b/website/src/writeRgba2.cpp @@ -1,3 +1,4 @@ +// [begin writeRgba2] void writeRgba2 ( const char fileName[], @@ -11,3 +12,21 @@ writeRgba2 ( file.setFrameBuffer (pixels, 1, width); file.writePixels (dataWindow.max.y - dataWindow.min.y + 1); } +// [end writeRgba2] + +void +writeRgba2ResizeFrameBuffer ( + const char fileName[], + const Rgba* pixels, + int width, + int height, + const Box2i& dataWindow) +{ + Box2i displayWindow (V2i (0, 0), V2i (width - 1, height - 1)); + RgbaOutputFile file (fileName, displayWindow, dataWindow, WRITE_RGBA); + // [begin writeRgba2ResizeFrameBuffer] + int dwWidth = dataWindow.max.x - dataWindow.min.x + 1; + file.setFrameBuffer (pixels - dataWindow.min.x - dataWindow.min.y * dwWidth, 1, dwWidth); + // [end writeRgba2ResizeFrameBuffer] +} + diff --git a/website/src/writeRgbaWithPreview2.cpp b/website/src/writeRgbaWithPreview2.cpp index d3e10faed3..93506e54a3 100644 --- a/website/src/writeRgbaWithPreview2.cpp +++ b/website/src/writeRgbaWithPreview2.cpp @@ -31,7 +31,7 @@ writeRgbaWithPreview2 (const char fileName[], int width, int height) outPixel.r = gamma (inPixel.r); outPixel.g = gamma (inPixel.g); outPixel.b = gamma (inPixel.b); - outPixel.a = int (clamp (inPixel.a * 255.f, 0.f, 255.f) + 0.5f); + outPixel.a = int (Imath::clamp (inPixel.a * 255.f, 0.f, 255.f) + 0.5f); } } } diff --git a/website/src/writer/build.sh b/website/src/writer/build.sh deleted file mode 100755 index 7cd8c5801a..0000000000 --- a/website/src/writer/build.sh +++ /dev/null @@ -1,3 +0,0 @@ -$ mkdir _build -$ cmake -S . -B _build -$ cmake --build _build diff --git a/website/tools.rst b/website/tools.rst index c100cb2331..24bd5e881f 100644 --- a/website/tools.rst +++ b/website/tools.rst @@ -14,6 +14,15 @@ operations, consider `oiiotool Swiss Army Knife of `OpenImageIO `_ +The OpenEXR tools are not generally included in most package managers' +distribution of OpenEXR libraries (e.g. via ``yum install``). To build +the tools from source, configure the top-level OpenEXR project build +with the cmake option ``OPENEXR_BUILD_TOOLS=ON``. The tools can only +be built from source as a component of the overall project build, not +separately. To further include the tools in the OpenEXR installation +after build (i.e. ``cmake --target install``), configure with +``OPENEXR_INSTALL_TOOLS=ON``. Both are on by default. + .. toctree:: :caption: Tools :titlesonly: @@ -25,6 +34,7 @@ Swiss Army Knife of `OpenImageIO bin/exrinfo bin/exrmakepreview bin/exrmaketiled + bin/exrmanifest bin/exrmultipart bin/exrmultiview bin/exrstdattr