diff --git a/.bazelversion b/.bazelversion index 91e4a9f262..66ce77b7ea 100644 --- a/.bazelversion +++ b/.bazelversion @@ -1 +1 @@ -6.3.2 +7.0.0 diff --git a/.github/workflows/bazel_build.yml b/.github/workflows/bazel_build.yml index 6ed03b4c95..fc18837ed5 100644 --- a/.github/workflows/bazel_build.yml +++ b/.github/workflows/bazel_build.yml @@ -57,9 +57,9 @@ jobs: run: | bazelisk build //... bazelisk test //... - # Test bzlmod - bazelisk build --enable_bzlmod -- //... - bazelisk test --enable_bzlmod -- //... + # Test without bzlmod + bazelisk build --noenable_bzlmod -- //... + bazelisk test --noenable_bzlmod -- //... build_and_test_windows: name: Windows Server 2022 build @@ -78,9 +78,9 @@ jobs: run: | bazelisk build //... bazelisk test //... - # Test bzlmod - bazelisk build --enable_bzlmod -- //... - bazelisk test --enable_bzlmod -- //... + # Test without bzlmod + bazelisk build --noenable_bzlmod -- //... + bazelisk test --noenable_bzlmod -- //... build_and_test_macos: name: macOS 13 Bazel build @@ -99,6 +99,6 @@ jobs: run: | bazelisk build //... bazelisk test //... - # Test bzlmod - bazelisk build --enable_bzlmod -- //... - bazelisk test --enable_bzlmod -- //... + # Test without bzlmod + bazelisk build --noenable_bzlmod -- //... + bazelisk test --noenable_bzlmod -- //... diff --git a/.github/workflows/release-notice.yml b/.github/workflows/release-notice.yml new file mode 100644 index 0000000000..cee8382235 --- /dev/null +++ b/.github/workflows/release-notice.yml @@ -0,0 +1,36 @@ +# 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/other/aswf/text/aqua/aswf-text-aqua.png" + 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/snyk-scan-pr.yml b/.github/workflows/snyk-scan-pr.yml new file mode 100644 index 0000000000..5800833ee1 --- /dev/null +++ b/.github/workflows/snyk-scan-pr.yml @@ -0,0 +1,45 @@ +--- +# 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 + pull_request: + branches: + - main + paths: + - '**.h' + - '**.c' + - '**.cpp' + +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/.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/CHANGES.md b/CHANGES.md index 5d52fd480b..2cc1309a4c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -23,6 +23,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 @@ -1379,6 +1381,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: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9a9ba9cf65..b17ad219a2 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 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..464b846470 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.9", repo_name = "Imath") 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/bazel/third_party/openexr_deps.bzl b/bazel/third_party/openexr_deps.bzl index c0833d1c48..5bbd57adf4 100644 --- a/bazel/third_party/openexr_deps.bzl +++ b/bazel/third_party/openexr_deps.bzl @@ -30,9 +30,9 @@ def openexr_deps(): maybe( http_archive, name = "bazel_skylib", - sha256 = "66ffd9315665bfaafc96b52278f57c7e2dd09f5ede279ea6d39b2be471e7e3aa", + sha256 = "cd55a062e763b9349921f0f5db8c3933288dc8ba4f76dd9416aac68acee3cb94", 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", + "https://mirror.bazel.build/github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz", + "https://github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz", ], ) 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 7d01362c84..a70f86d2f4 100644 --- a/cmake/OpenEXRSetup.cmake +++ b/cmake/OpenEXRSetup.cmake @@ -160,15 +160,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 +238,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() ####################################### diff --git a/src/bin/CMakeLists.txt b/src/bin/CMakeLists.txt index 9a76d58071..893fd58d11 100644 --- a/src/bin/CMakeLists.txt +++ b/src/bin/CMakeLists.txt @@ -15,4 +15,5 @@ if(OPENEXR_BUILD_TOOLS) add_subdirectory( exrmultiview ) add_subdirectory( exrmultipart ) add_subdirectory( exrcheck ) + add_subdirectory( exrmanifest ) endif() 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/examples/CMakeLists.txt b/src/examples/CMakeLists.txt index 4d8cb57eb7..ea34cb42c9 100644 --- a/src/examples/CMakeLists.txt +++ b/src/examples/CMakeLists.txt @@ -41,6 +41,16 @@ target_link_libraries(OpenEXRExamples OpenEXR::OpenEXR) set_target_properties(OpenEXRExamples PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin" ) + +add_executable(deepidexample deepidexample.cpp) +target_link_libraries(deepidexample OpenEXR::OpenEXR) + +add_executable(deepidselect deepidselect.cpp) +target_link_libraries(deepidselect OpenEXR::OpenEXR) + + + + if(WIN32 AND BUILD_SHARED_LIBS) target_compile_definitions(OpenEXRExamples PRIVATE OPENEXR_DLL) endif() diff --git a/src/examples/deepidexample.cpp b/src/examples/deepidexample.cpp new file mode 100644 index 0000000000..50e9c90b4d --- /dev/null +++ b/src/examples/deepidexample.cpp @@ -0,0 +1,689 @@ +// +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) Contributors to the OpenEXR Project. +// + +// +// example of writing a file with DeepIDs. +// Creates a deep file with a series of circles and 'blobs' (2D Guassian-like objects) +// of different colors and sizes, each with two or three ID channels, and an idmanifest +// to store the respective names. +// +// + +#include +#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/lib/OpenEXRCore/internal_dwa_compressor.h b/src/lib/OpenEXRCore/internal_dwa_compressor.h index 03ad606695..729b0d7425 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; } } 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/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/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/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/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/install.rst b/website/install.rst index 8a0d265861..7f1d8aee27 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,31 @@ 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. +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 ----------------- diff --git a/website/news.rst b/website/news.rst index 86cac87927..76b14fe7d7 100644 --- a/website/news.rst +++ b/website/news.rst @@ -13,6 +13,12 @@ News .. toctree:: :caption: News +December 19, 2023 - OpenEXR v2.5.10 Released +============================================ + +Patch release for OpenEXR v2.5 that fixes a build failure on macOS +prior to 10.6 (fallback for missing `libdispatch`). + September 20, 2023 - ASWF Dev Days ================================== 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/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/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); } } }