├── .clang-format ├── .cmake-format ├── .cmake-format.json ├── .github └── workflows │ ├── ci-emscripten.yml │ ├── ci-linux.yml │ ├── ci-mac.yml │ ├── ci-windows.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE.txt ├── README.md ├── TODO.md ├── assets ├── app_settings │ ├── apple │ │ └── Info.plist │ ├── emscripten │ │ ├── coi-serviceworker.js │ │ └── shell.emscripten.html │ └── icon.png ├── fonts │ ├── MaterialIcons-Regular.ttf │ ├── MaterialSymbolsRounded_Filled-Regular.ttf │ ├── Roboto │ │ ├── LICENSE.txt │ │ ├── Roboto-Bold.ttf │ │ ├── Roboto-BoldItalic.ttf │ │ ├── Roboto-Regular.ttf │ │ ├── Roboto-RegularItalic.ttf │ │ ├── RobotoMono-Bold.ttf │ │ └── RobotoMono-Regular.ttf │ ├── lucide.ttf │ └── materialdesignicons-webfont.ttf └── shaders │ ├── colorspaces.glsl │ ├── colorspaces.metal │ ├── image-shader_frag.glsl │ ├── image-shader_frag.metal │ ├── image-shader_vert.glsl │ └── image-shader_vert.metal ├── cmake ├── CPM.cmake ├── VersionFromGit.cmake ├── generate_version.cmake ├── modules │ ├── FindJXL.cmake │ ├── FindLCMS2.cmake │ ├── FindLibheif.cmake │ └── Findlibuhdr.cmake ├── osx-post-install.cmake └── sanitizers.cmake ├── custom_http_server.py ├── resources ├── gamma-grid.exr ├── gamma-grid.png ├── icon.pdf ├── icon.psd ├── sample-colormap.py ├── screenshot-command-palette.png ├── screenshot-dithered.png ├── screenshot-ipad.jpg ├── screenshot-mac.png ├── screenshot-no-dither.png └── screenshot-zoomed.png └── src ├── Imath_to_linalg.h ├── app.cpp ├── app.h ├── array2d.h ├── async.h ├── box.h ├── cliformatter.h ├── colormap.cpp ├── colormap.h ├── colorspace.cpp ├── colorspace.h ├── common.cpp ├── common.h ├── dithermatrix256.h ├── emscripten_utils.cpp ├── emscripten_utils.h ├── fonts.cpp ├── fonts.h ├── fwd.h ├── hdrview.cpp ├── icons ├── IconsLucide.h ├── IconsMaterialDesign.h ├── IconsMaterialDesignIcons.h └── IconsMaterialSymbols.h ├── image.cpp ├── image.h ├── image_gui.cpp ├── imageio.cpp ├── imageio ├── exr.cpp ├── exr.h ├── exr_std_streams.h ├── heif.cpp ├── heif.h ├── icc.cpp ├── icc.h ├── jxl.cpp ├── jxl.h ├── pfm.cpp ├── pfm.h ├── qoi.cpp ├── qoi.h ├── stb.cpp ├── stb.h ├── uhdr.cpp └── uhdr.h ├── imgui_ext.cpp ├── imgui_ext.h ├── json.h ├── opengl_check.cpp ├── opengl_check.h ├── progress.cpp ├── progress.h ├── renderpass.h ├── renderpass_gl.cpp ├── renderpass_metal.mm ├── ringbuffer_color_sink.h ├── scheduler.cpp ├── scheduler.h ├── shader.cpp ├── shader.h ├── shader_gl.cpp ├── shader_metal.mm ├── texture.cpp ├── texture.h ├── texture_gl.cpp ├── texture_metal.mm ├── timer.h ├── traits.h ├── version.cpp.in └── version.h /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Microsoft 3 | TabWidth: 4 4 | IndentWidth: 4 5 | ColumnLimit: 120 6 | Standard: Cpp11 7 | 8 | AlignAfterOpenBracket: Align 9 | AlignConsecutiveMacros: 'true' 10 | AlignConsecutiveAssignments: 'true' 11 | AlignConsecutiveDeclarations: 'true' 12 | AlignTrailingComments: 'true' 13 | 14 | AllowShortFunctionsOnASingleLine: All 15 | AllowShortLambdasOnASingleLine: All 16 | AllowShortBlocksOnASingleLine: Always 17 | AllowShortLoopsOnASingleLine: 'true' 18 | AllowShortCaseLabelsOnASingleLine: 'true' 19 | 20 | AccessModifierOffset: -4 21 | AlwaysBreakTemplateDeclarations: Yes 22 | BreakConstructorInitializers: AfterColon 23 | 24 | BreakBeforeBraces: Allman 25 | 26 | ... 27 | -------------------------------------------------------------------------------- /.cmake-format: -------------------------------------------------------------------------------- 1 | format: 2 | tab_size: 2 3 | line_width: 120 4 | dangle_parens: true 5 | 6 | parse: 7 | additional_commands: 8 | cpmaddpackage: 9 | pargs: 10 | nargs: "*" 11 | flags: [] 12 | spelling: CPMAddPackage 13 | kwargs: &cpmaddpackagekwargs 14 | NAME: 1 15 | FORCE: 1 16 | VERSION: 1 17 | GIT_TAG: 1 18 | DOWNLOAD_ONLY: 1 19 | GITHUB_REPOSITORY: 1 20 | GITLAB_REPOSITORY: 1 21 | GIT_REPOSITORY: 1 22 | SVN_REPOSITORY: 1 23 | SVN_REVISION: 1 24 | SOURCE_DIR: 1 25 | DOWNLOAD_COMMAND: 1 26 | FIND_PACKAGE_ARGUMENTS: 1 27 | NO_CACHE: 1 28 | GIT_SHALLOW: 1 29 | URL: 1 30 | URL_HASH: 1 31 | URL_MD5: 1 32 | DOWNLOAD_NAME: 1 33 | DOWNLOAD_NO_EXTRACT: 1 34 | HTTP_USERNAME: 1 35 | HTTP_PASSWORD: 1 36 | OPTIONS: + 37 | cpmfindpackage: 38 | pargs: 39 | nargs: "*" 40 | flags: [] 41 | spelling: CPMFindPackage 42 | kwargs: *cpmaddpackagekwargs 43 | packageproject: 44 | pargs: 45 | nargs: "*" 46 | flags: [] 47 | spelling: packageProject 48 | kwargs: 49 | NAME: 1 50 | VERSION: 1 51 | NAMESPACE: 1 52 | INCLUDE_DIR: 1 53 | INCLUDE_DESTINATION: 1 54 | BINARY_DIR: 1 55 | COMPATIBILITY: 1 56 | VERSION_HEADER: 1 57 | DEPENDENCIES: + 58 | -------------------------------------------------------------------------------- /.github/workflows/ci-emscripten.yml: -------------------------------------------------------------------------------- 1 | name: Emscripten development build 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "*" 7 | push: 8 | branches: 9 | - "*" 10 | paths: 11 | # This action only runs on push when files affecting the build change 12 | - "**.cpp" 13 | - "**.h" 14 | - "**.glsl" 15 | - "**.cmake" 16 | - "**Lists.txt" 17 | - "**-emscripten.yml" 18 | workflow_dispatch: 19 | 20 | env: 21 | CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm_modules 22 | 23 | jobs: 24 | build_emscripten: 25 | name: Build and deploy emscripten webapp 26 | runs-on: macos-14 27 | 28 | steps: 29 | - name: Install dependencies 30 | run: | 31 | brew install ninja emscripten 32 | 33 | - uses: actions/checkout@v4 34 | with: 35 | fetch-depth: 0 36 | 37 | - uses: actions/cache@v4 38 | with: 39 | path: "**/cpm_modules" 40 | key: ${{ github.workflow }}-cpm-modules-${{ hashFiles('**/CMakeLists.txt', '**/*.cmake') }} 41 | 42 | # Setup caching of build artifacts to reduce total build time (only Linux and MacOS) 43 | - name: ccache 44 | uses: hendrikmuhs/ccache-action@v1.2 45 | 46 | - name: Configure CMake 47 | run: | 48 | emcmake cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/build/deploy 49 | 50 | - name: Build 51 | run: cmake --build ${{github.workspace}}/build --parallel 52 | 53 | - name: Archive build artifacts 54 | uses: actions/upload-artifact@v4 55 | with: 56 | name: build-artifacts-emscripten 57 | path: | 58 | ${{github.workspace}}/build 59 | 60 | - name: CPack 61 | working-directory: ${{github.workspace}}/build 62 | run: | 63 | cmake --install . 64 | mv deploy/HDRView.html deploy/index.html 65 | 66 | - name: Publish 67 | uses: peaceiris/actions-gh-pages@v4 68 | with: 69 | personal_token: ${{ secrets.GITHUB_TOKEN }} 70 | publish_branch: gh-pages 71 | destination_dir: dev 72 | publish_dir: ${{github.workspace}}/build/deploy 73 | -------------------------------------------------------------------------------- /.github/workflows/ci-linux.yml: -------------------------------------------------------------------------------- 1 | name: Linux build 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "*" 7 | push: 8 | branches: 9 | - "*" 10 | paths: 11 | # This action only runs on push when files affecting the build change 12 | - "**.cpp" 13 | - "**.h" 14 | - "**.glsl" 15 | - "**.cmake" 16 | - "**Lists.txt" 17 | - "**-linux.yml" 18 | workflow_dispatch: 19 | 20 | env: 21 | CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm_modules 22 | 23 | jobs: 24 | build: 25 | name: ${{ matrix.config.name }} (${{ matrix.buildtype }}) 26 | runs-on: ${{ matrix.config.os }} 27 | strategy: 28 | fail-fast: false 29 | matrix: 30 | config: 31 | - { name: "Ubuntu 22.04", os: ubuntu-22.04 } 32 | - { name: "Ubuntu 20.04", os: ubuntu-20.04 } 33 | buildtype: [Release, Debug] 34 | 35 | steps: 36 | - name: Install dependencies 37 | run: sudo apt-get update && sudo apt-get install cmake xorg-dev libglu1-mesa-dev zlib1g-dev libxrandr-dev ninja-build libglfw3-dev libfreetype-dev 38 | 39 | - uses: actions/checkout@v4 40 | with: 41 | fetch-depth: 0 42 | 43 | - uses: actions/cache@v4 44 | with: 45 | path: "**/cpm_modules" 46 | key: ${{ github.workflow }}-cpm-modules-${{ hashFiles('**/CMakeLists.txt', '**/*.cmake') }} 47 | 48 | # Setup caching of build artifacts to reduce total build time (only Linux and MacOS) 49 | - name: ccache 50 | uses: hendrikmuhs/ccache-action@v1.2 51 | with: 52 | key: ${{ matrix.config.os }}-${{ matrix.buildtype }} 53 | 54 | - name: Configure CMake 55 | run: | 56 | cmake -B ${{github.workspace}}/build/${{ matrix.buildtype }} -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.buildtype }} -DCMAKE_POLICY_DEFAULT_CMP0135=NEW -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DHELLOIMGUI_DOWNLOAD_FREETYPE_IF_NEEDED=ON 57 | 58 | - name: Build 59 | run: cmake --build ${{github.workspace}}/build/${{ matrix.buildtype }} --parallel 4 --config ${{ matrix.buildtype }} 60 | 61 | - name: Checking that HDRView runs 62 | run: | 63 | ${{github.workspace}}/build/${{ matrix.buildtype }}/HDRView --help 64 | 65 | - name: Archive build artifacts 66 | uses: actions/upload-artifact@v4 67 | with: 68 | name: build-artifacts-${{ matrix.config.os }}-${{ matrix.buildtype }} 69 | path: | 70 | ${{github.workspace}}/build/${{ matrix.buildtype }} 71 | -------------------------------------------------------------------------------- /.github/workflows/ci-mac.yml: -------------------------------------------------------------------------------- 1 | name: macOS build 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "*" 7 | push: 8 | branches: 9 | - "*" 10 | paths: 11 | # This action only runs on push when files affecting the build change 12 | - "**.cpp" 13 | - "**.h" 14 | - "**.metal" 15 | - "**.cmake" 16 | - "**Lists.txt" 17 | - "**-mac.yml" 18 | workflow_dispatch: 19 | 20 | env: 21 | CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm_modules 22 | 23 | jobs: 24 | build: 25 | name: ${{ matrix.config.name }} (${{ matrix.buildtype }}) 26 | runs-on: ${{ matrix.config.os }} 27 | strategy: 28 | fail-fast: false 29 | matrix: 30 | config: 31 | # macos-13 is an intel x86_64 runner 32 | # macos-14 is an arm64 runner 33 | - { 34 | name: "macOS 14 universal", 35 | os: macos-14, 36 | arch: "universal", 37 | flags: '-DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" -DCMAKE_IGNORE_PATH="/opt/homebrew/Cellar/harfbuzz/10.4.0/include/;/opt/homebrew/Cellar/harfbuzz/10.4.0/lib/;/opt/homebrew/Cellar/brotli/1.1.0/include;/opt/homebrew/Cellar/brotli/1.1.0/lib/;/opt/homebrew/lib;/opt/homebrew/include;/opt/homebrew/Cellar" -DHDRVIEW_ENABLE_UHDR=OFF -DHELLOIMGUI_FREETYPE_STATIC=ON', 38 | } 39 | - { 40 | name: "macOS 13 universal", 41 | os: macos-13, 42 | arch: "universal", 43 | flags: '-DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" -DCMAKE_IGNORE_PATH="/usr/local/Cellar/harfbuzz/10.4.0/lib/;/usr/local/Cellar/harfbuzz/10.4.0/;/usr/local/Cellar/brotli/1.1.0/include;/usr/local/Cellar/brotli/1.1.0/lib/;/usr/local/lib/;/usr/local/Cellar/" -DHDRVIEW_ENABLE_UHDR=OFF -DHELLOIMGUI_FREETYPE_STATIC=ON', 44 | } 45 | - { 46 | name: "macOS 14 Apple Silicon", 47 | os: macos-14, 48 | arch: "arm64", 49 | flags: "-DHDRVIEW_ENABLE_HEIF=ON -DHDRVIEW_ENABLE_JPEGXL=ON -DHDRVIEW_ENABLE_UHDR=ON -DUHDR_BUILD_DEPS=ON", 50 | } 51 | - { 52 | name: "macOS 13 Intel", 53 | os: macos-13, 54 | arch: "x86_64", 55 | flags: "-DHDRVIEW_ENABLE_HEIF=ON -DHDRVIEW_ENABLE_JPEGXL=ON -DHDRVIEW_ENABLE_UHDR=ON -DUHDR_BUILD_DEPS=ON", 56 | } 57 | 58 | buildtype: [Release, Debug] 59 | 60 | steps: 61 | - name: Install dependencies 62 | run: brew install ninja create-dmg dylibbundler jpeg-xl little-cms2 libultrahdr libheif imath cli11 spdlog fmt aom libde265 x265 63 | 64 | - uses: actions/checkout@v4 65 | with: 66 | fetch-depth: 0 67 | 68 | - uses: actions/cache@v4 69 | with: 70 | path: "**/cpm_modules" 71 | key: ${{ github.workflow }}-cpm-modules-${{ hashFiles('**/CMakeLists.txt', '**/*.cmake') }} 72 | 73 | # Setup caching of build artifacts to reduce total build time (only Linux and MacOS) 74 | - name: ccache 75 | uses: hendrikmuhs/ccache-action@v1.2 76 | with: 77 | key: ${{ matrix.config.os }}-${{ matrix.config.arch }}-${{ matrix.buildtype }} 78 | 79 | - name: Configure CMake 80 | run: | 81 | cmake -B ${{github.workspace}}/build/${{ matrix.config.arch }}-${{ matrix.buildtype }} -G Ninja -DCMAKE_BUILD_TYPE=${{ matrix.buildtype }} -DCMAKE_POLICY_DEFAULT_CMP0135=NEW -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 ${{ matrix.config.flags }} 82 | 83 | - name: Build 84 | run: cmake --build ${{github.workspace}}/build/${{ matrix.config.arch }}-${{ matrix.buildtype }} --parallel --config ${{ matrix.buildtype }} 85 | 86 | - name: Checking that HDRView runs 87 | run: | 88 | ${{github.workspace}}/build/${{ matrix.config.arch }}-${{ matrix.buildtype }}/HDRView.app/Contents/MacOS/HDRView --help 89 | 90 | - name: Bundle dependencies 91 | run: dylibbundler -od -b -x ${{github.workspace}}/build/${{ matrix.config.arch }}-${{ matrix.buildtype }}/HDRView.app/Contents/MacOS/HDRView -d ${{github.workspace}}/build/${{ matrix.config.arch }}-${{ matrix.buildtype }}/HDRView.app/Contents/libs/ 92 | 93 | - name: Creating dmg (macOS) 94 | run: | 95 | RESULT="${{github.workspace}}/build/${{ matrix.config.arch }}-${{ matrix.buildtype }}/HDRView.dmg" 96 | test -f $RESULT && rm $RESULT 97 | create-dmg --window-size 500 300 --icon-size 96 --volname "HDRView Installer" --app-drop-link 360 105 --icon HDRView.app 130 105 $RESULT ${{github.workspace}}/build/${{ matrix.config.arch }}-${{ matrix.buildtype }}/HDRView.app 98 | 99 | - name: Archive dmg (macOS) 100 | uses: actions/upload-artifact@v4 101 | with: 102 | name: HDRView-${{ matrix.config.os }}-${{ matrix.config.arch }}-${{ matrix.buildtype }}.dmg 103 | path: | 104 | ${{github.workspace}}/build/${{ matrix.config.arch }}-${{ matrix.buildtype }}/HDRView.dmg 105 | 106 | - name: Archive build artifacts 107 | uses: actions/upload-artifact@v4 108 | with: 109 | name: build-artifacts-${{ matrix.config.os }}-${{ matrix.config.arch }}-${{ matrix.buildtype }} 110 | path: | 111 | ${{github.workspace}}/build/${{ matrix.config.arch }}-${{ matrix.buildtype }} 112 | -------------------------------------------------------------------------------- /.github/workflows/ci-windows.yml: -------------------------------------------------------------------------------- 1 | name: Windows build 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "*" 7 | push: 8 | branches: 9 | - "*" 10 | paths: 11 | # This action only runs on push when files affecting the build change 12 | - "**.cpp" 13 | - "**.h" 14 | - "**.glsl" 15 | - "**.cmake" 16 | - "**Lists.txt" 17 | - "**-windows.yml" 18 | workflow_dispatch: 19 | 20 | jobs: 21 | build: 22 | name: ${{ matrix.config.name }} (${{ matrix.buildtype }}) 23 | runs-on: ${{ matrix.config.os }} 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | buildtype: [Release, Debug] 28 | config: 29 | - { name: "Windows", os: windows-latest } 30 | 31 | steps: 32 | - name: Install dependencies 33 | run: pip install Pillow 34 | 35 | - uses: actions/checkout@v4 36 | with: 37 | fetch-depth: 0 38 | 39 | - name: Configure CMake 40 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{ matrix.buildtype }} -DCMAKE_POLICY_DEFAULT_CMP0135=NEW -DHDRVIEW_ENABLE_HEIF=ON -DHDRVIEW_ENABLE_JPEGXL=ON -DHDRVIEW_ENABLE_UHDR=ON -DUHDR_BUILD_DEPS=ON 41 | 42 | - name: Build 43 | run: cmake --build ${{github.workspace}}/build --parallel --config ${{ matrix.buildtype }} --verbose 44 | 45 | - name: Checking that HDRView runs 46 | working-directory: ${{github.workspace}} 47 | run: ./build/${{ matrix.buildtype }}/HDRView.exe --help 48 | 49 | - name: Copy files for archiving 50 | working-directory: ${{github.workspace}}/build 51 | run: | 52 | cmake -E copy_directory assets deploy/assets/ 53 | cmake -E copy ${{ matrix.buildtype }}/HDRView.exe deploy/ 54 | 55 | - name: Archive build artifacts 56 | uses: actions/upload-artifact@v4 57 | with: 58 | name: build-artifacts-${{ matrix.config.os }}-${{ matrix.buildtype }} 59 | path: | 60 | ${{github.workspace}}/build/deploy 61 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish to releases and github.io website 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v*.*" 7 | - "v*.*.*" 8 | workflow_dispatch: 9 | 10 | env: 11 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 12 | BUILD_TYPE: Release 13 | CPM_SOURCE_CACHE: ${{ github.workspace }}/cpm_modules 14 | 15 | jobs: 16 | build_macos: 17 | name: Build on ${{ matrix.config.name }} 18 | runs-on: ${{ matrix.config.os }} 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | config: 23 | # macos-13 is an intel x86_64 runner 24 | # macos-14 is an arm64 runner 25 | # 26 | # Disabling universal build until libultrahdr fixes its cross-compilation 27 | # - { 28 | # name: "macOS Universal", 29 | # os: macos-14, 30 | # arch: "universal", 31 | # flags: '-DCMAKE_OSX_ARCHITECTURES="arm64;x86_64" -DCMAKE_IGNORE_PATH="/opt/homebrew/Cellar/harfbuzz/10.1.0/include/;/opt/homebrew/Cellar/harfbuzz/10.1.0/lib/;/usr/local/Cellar/harfbuzz/10.1.0/lib/;/usr/local/Cellar/harfbuzz/10.1.0/;/opt/homebrew/Cellar/brotli/1.1.0/include;/opt/homebrew/Cellar/brotli/1.1.0/lib/;/usr/local/Cellar/brotli/1.1.0/include;/usr/local/Cellar/brotli/1.1.0/lib/;/usr/local/Cellar/;/opt/homebrew/lib;/opt/homebrew/include;/opt/homebrew/Cellar" -DHDRVIEW_ENABLE_UHDR=OFF -DHELLOIMGUI_FREETYPE_STATIC=ON', 32 | # } 33 | - { 34 | name: "macOS Apple Silicon", 35 | os: macos-14, 36 | arch: "arm64", 37 | flags: "-DHDRVIEW_ENABLE_HEIF=ON -DHDRVIEW_ENABLE_JPEGXL=ON -DHDRVIEW_ENABLE_UHDR=ON -DUHDR_BUILD_DEPS=ON", 38 | } 39 | - { 40 | name: "macOS Intel", 41 | os: macos-13, 42 | arch: "x86_64", 43 | flags: "-DHDRVIEW_ENABLE_HEIF=ON -DHDRVIEW_ENABLE_JPEGXL=ON -DHDRVIEW_ENABLE_UHDR=ON -DUHDR_BUILD_DEPS=ON", 44 | } 45 | 46 | steps: 47 | - name: Install dependencies 48 | run: brew install ninja create-dmg dylibbundler jpeg-xl little-cms2 libultrahdr libheif imath cli11 spdlog fmt aom libde265 x265 49 | 50 | - uses: actions/checkout@v4 51 | with: 52 | fetch-depth: 0 53 | 54 | - uses: actions/cache@v4 55 | with: 56 | path: "**/cpm_modules" 57 | key: ${{ github.workflow }}-cpm-modules-${{ hashFiles('**/CMakeLists.txt', '**/*.cmake') }} 58 | 59 | # Setup caching of build artifacts to reduce total build time (only Linux and MacOS) 60 | - name: ccache 61 | uses: hendrikmuhs/ccache-action@v1.2 62 | with: 63 | key: ${{ matrix.config.os }}-${{ matrix.config.arch }} 64 | 65 | - name: Configure CMake 66 | run: | 67 | cmake -B ${{github.workspace}}/build/${{ matrix.config.arch }} -G Ninja -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_POLICY_DEFAULT_CMP0135=NEW -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_OSX_DEPLOYMENT_TARGET=10.15 ${{ matrix.config.flags }} 68 | 69 | - name: Build 70 | run: cmake --build ${{github.workspace}}/build/${{ matrix.config.arch }} --parallel --config ${{env.BUILD_TYPE}} 71 | 72 | - name: Checking that HDRView runs 73 | run: | 74 | ${{github.workspace}}/build/${{ matrix.config.arch }}/HDRView.app/Contents/MacOS/HDRView --help 75 | 76 | - name: Bundle dependencies 77 | run: dylibbundler -od -b -x ${{github.workspace}}/build/${{ matrix.config.arch }}/HDRView.app/Contents/MacOS/HDRView -d ${{github.workspace}}/build/${{ matrix.config.arch }}/HDRView.app/Contents/libs/ 78 | 79 | - name: Creating dmg 80 | run: | 81 | RESULT="${{github.workspace}}/build/${{ matrix.config.arch }}/HDRView-${{ matrix.config.arch }}.dmg" 82 | test -f $RESULT && rm $RESULT 83 | create-dmg --window-size 500 300 --icon-size 96 --volname "HDRView Installer" --app-drop-link 360 105 --icon HDRView.app 130 105 $RESULT ${{github.workspace}}/build/${{ matrix.config.arch }}/HDRView.app 84 | 85 | - name: Archive build artifacts 86 | uses: actions/upload-artifact@v4 87 | with: 88 | name: build-artifacts-${{ matrix.config.os }}-${{ matrix.config.arch }} 89 | path: | 90 | ${{github.workspace}}/build/${{ matrix.config.arch }} 91 | 92 | - name: Release 93 | uses: softprops/action-gh-release@v2 94 | with: 95 | files: ${{github.workspace}}/build/${{ matrix.config.arch }}/HDRView-${{ matrix.config.arch }}.dmg 96 | env: 97 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 98 | 99 | build_windows: 100 | name: Build on Windows 101 | runs-on: windows-latest 102 | 103 | steps: 104 | - name: Install dependencies 105 | run: pip install Pillow 106 | 107 | - uses: actions/checkout@v4 108 | with: 109 | fetch-depth: 0 110 | 111 | - name: Configure CMake 112 | run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_POLICY_DEFAULT_CMP0135=NEW -DHDRVIEW_ENABLE_HEIF=ON -DHDRVIEW_ENABLE_JPEGXL=ON -DHDRVIEW_ENABLE_UHDR=ON -DUHDR_BUILD_DEPS=ON 113 | 114 | - name: Build 115 | run: cmake --build ${{github.workspace}}/build --parallel --config ${{env.BUILD_TYPE}} --verbose 116 | 117 | - name: Checking that HDRView runs 118 | working-directory: ${{github.workspace}} 119 | run: ./build/${{env.BUILD_TYPE}}/HDRView.exe --help 120 | 121 | - name: Copy files for archiving and release 122 | working-directory: ${{github.workspace}}/build 123 | run: | 124 | cmake -E copy_directory assets deploy/assets/ 125 | cmake -E copy ${{env.BUILD_TYPE}}/HDRView.exe deploy/ 126 | 127 | - name: Archive build artifacts 128 | uses: actions/upload-artifact@v4 129 | with: 130 | name: build-artifacts-windows-latest 131 | path: | 132 | ${{github.workspace}}/build/deploy 133 | 134 | - name: Archive Release 135 | uses: thedoctor0/zip-release@0.7.5 136 | with: 137 | type: "zip" 138 | filename: "HDRView-windows.zip" 139 | directory: ${{github.workspace}}/build/deploy 140 | 141 | - name: Release 142 | uses: softprops/action-gh-release@v2 143 | with: 144 | files: | 145 | ${{github.workspace}}/build/deploy/HDRView-windows.zip 146 | env: 147 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 148 | 149 | build_emscripten: 150 | name: Build and deploy emscripten webapp 151 | runs-on: macos-14 152 | 153 | steps: 154 | - name: Install dependencies 155 | run: | 156 | brew install ninja emscripten 157 | 158 | - uses: actions/checkout@v4 159 | with: 160 | fetch-depth: 0 161 | 162 | - uses: actions/cache@v4 163 | with: 164 | path: "**/cpm_modules" 165 | key: ${{ github.workflow }}-cpm-modules-${{ hashFiles('**/CMakeLists.txt', '**/*.cmake') }} 166 | 167 | # Setup caching of build artifacts to reduce total build time (only Linux and MacOS) 168 | - name: ccache 169 | uses: hendrikmuhs/ccache-action@v1.2 170 | 171 | - name: Configure CMake 172 | run: | 173 | emcmake cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=MinSizeRel -DCMAKE_INSTALL_PREFIX=${{github.workspace}}/build/deploy 174 | 175 | - name: Build 176 | run: cmake --build ${{github.workspace}}/build --parallel 177 | 178 | - name: Archive build artifacts 179 | uses: actions/upload-artifact@v4 180 | with: 181 | name: build-artifacts-emscripten 182 | path: | 183 | ${{github.workspace}}/build 184 | 185 | - name: CPack 186 | working-directory: ${{github.workspace}}/build 187 | run: | 188 | cmake --install . 189 | mv deploy/HDRView.html deploy/index.html 190 | 191 | - name: Publish 192 | uses: peaceiris/actions-gh-pages@v4 193 | with: 194 | personal_token: ${{ secrets.GITHUB_TOKEN }} 195 | publish_branch: gh-pages 196 | publish_dir: ${{github.workspace}}/build/deploy 197 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | 6 | # Compiled Dynamic libraries 7 | *.so 8 | *.dylib 9 | *.dll 10 | 11 | # Compiled Static libraries 12 | *.lai 13 | *.la 14 | *.a 15 | *.lib 16 | 17 | # CMake temporary files 18 | CMakeCache.txt 19 | CMakeFiles 20 | CPackConfig.cmake 21 | CPackSourceConfig.cmake 22 | Makefile 23 | cmake_install.cmake 24 | .ninja_deps 25 | .ninja_log 26 | build.ninja 27 | rules.ninja 28 | 29 | # Executables, build directories and miscellaneous 30 | *.exe 31 | .DS_Store 32 | ext_build 33 | .vscode 34 | build*/ 35 | cmake-build-debug 36 | cmake-build-release 37 | cmake-build-minsizerel 38 | cmake-build-relwithdebinfo 39 | .idea 40 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkjarosz/hdrview/5ef1c6258de36bead0aee8919e6748eb9fbe7829/.gitmodules -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Wojciech Jarosz. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright notice, this 7 | list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above copyright notice, 10 | this list of conditions and the following disclaimer in the documentation 11 | and/or other materials provided with the distribution. 12 | 13 | 3. Neither the name of the copyright holder nor the names of its contributors 14 | may be used to endorse or promote products derived from this software 15 | without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 18 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 21 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 22 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 23 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 24 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 26 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | 28 | You are under no obligation whatsoever to provide any bug fixes, patches, or 29 | upgrades to the features, functionality or performance of the source code 30 | ("Enhancements") to anyone; however, if you choose to make your Enhancements 31 | available either publicly, or directly to the authors of this software, without 32 | imposing a separate written license agreement for such Enhancements, then you 33 | hereby grant the following license: a non-exclusive, royalty-free perpetual 34 | license to install, use, modify, prepare derivative works, incorporate into 35 | other computer software, distribute, and sublicense such enhancements or 36 | derivative works thereof, in binary and source code form. 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HDRView 2 | 3 | Master branch: 4 | [![macOS build](https://github.com/wkjarosz/hdrview/actions/workflows/ci-mac.yml/badge.svg?branch=master)](https://github.com/wkjarosz/hdrview/actions/workflows/ci-mac.yml) 5 | [![Linux build](https://github.com/wkjarosz/hdrview/actions/workflows/ci-linux.yml/badge.svg?branch=master)](https://github.com/wkjarosz/hdrview/actions/workflows/ci-linux.yml) 6 | [![Windows build](https://github.com/wkjarosz/hdrview/actions/workflows/ci-windows.yml/badge.svg?branch=master)](https://github.com/wkjarosz/hdrview/actions/workflows/ci-windows.yml) 7 | 8 | HDRView is a simple research-oriented high-dynamic range image viewer with an emphasis on examining and comparing images. HDRView currently supports reading (EXR, HDR, PFM, and Ultra HDR JPEG) and writing (EXR, HDR, PFM) several HDR image formats, as well as reading (PNG, TGA, BMP, JPG, GIF, PNM, and PSD) and writing (PNG, TGA, PPM, PFM, and BMP) standard LDR images. 9 | 10 | HDRView can display images in true HDR on Apple extended dynamic range (EDR) and 10-bit displays. 11 | 12 | HDRView runs on macOS, Linux, Windows, and directly in your browser -- just go to [wkjarosz.github.io/hdrview/](https://wkjarosz.github.io/hdrview/) for the latest release version and [wkjarosz.github.io/hdrview/dev](https://wkjarosz.github.io/hdrview/dev) for the development version. This even works on an iPhone or iPad! Try it out. 13 | 14 | ## Example screenshots 15 | Here's a screenshot of HDRView viewing a JPEG on macOS: 16 | ![Screenshot](resources/screenshot-mac.png "Screenshot macOS") 17 | 18 | Or, running on an iPad as a webapp, viewing a luminance-chroma EXR image stored using XYZ primaries with chroma subsampling: 19 | ![Screenshot](resources/screenshot-ipad.jpg "Screenshot iPad") 20 | 21 | When sufficiently zoomed in, HDRView can overlay the pixel grid and numeric color values on each pixel to facilitate inspection: 22 | ![Screenshot](resources/screenshot-zoomed.png "Screenshot Zoomed-in") 23 | 24 | HDRView features extensive keyboard shortcuts, and pressing `Cmd+Shift+P` brings up a VS Code/Atom/Sublime Text-style command palette allowing you to find any command with keyboard-based fuzzy searching: 25 | ![Screenshot](resources/screenshot-command-palette.png "Screenshot of command palette") 26 | 27 | HDRView supports the extended dynamic range (XDR, 30 bit) capabilities of recent Macs, allowing it to use finer precision (reducing banding) and brighter whites (reducing clipping) when displaying HDR images. 28 | 29 | When displaying images on a standard dynamic range (SDR, 24 bit) display (or saving to an LDR file format), HDRView uses blue-noise dithering: 30 | ![Screenshot](resources/screenshot-dithered.png "Screenshot dithering on") 31 | 32 | This reduces apparent banding artifacts in smooth gradients compared to naively displaying HDR images on such displays: 33 | ![Screenshot](resources/screenshot-no-dither.png "Screenshot dithering off") 34 | 35 | 36 | ## Obtaining/running HDRView 37 | 38 | If you are running a recent version of macOS or Windows, you can download the pre-built binary installer DMG or zip file from the [releases page](https://github.com/wkjarosz/hdrview/releases). For Linux, you will need to build HDRView from source for now. Or, just run the [web app version](https://wkjarosz.github.io/hdrview/) directly in your browser. 39 | 40 | ## Compiling 41 | 42 | Compiling from scratch requires CMake and a recent version of the XCode build tools on macOS, Visual Studio on Windows, and GCC on Linux. 43 | 44 | Compiling should be as simple as: 45 | ```bash 46 | 1 ~ % git clone https://github.com/wkjarosz/hdrview.git 47 | 2 ~ % cd hdrview 48 | 3 ~ % mkdir build 49 | 4 ~ % cmake -B build 50 | 5 ~ % cmake --build build/ --parallel 4 51 | ``` 52 | 53 | On macOS and Linux you can add `-G Ninja` to line 4 (on Windows Ninja fails to build the OpenEXR dependency). 54 | 55 | To support Ultra HDR JPEG images, you will either need to have the dependencies for `libultrahdr` (namely `libjpeg-turbo`) installed, or pass the option `-DUHDR_BUILD_DEPS=ON` in line 4. 56 | 57 | Alternatively, you should be able to do all this via VS Code if you have the CMake extension set up properly. 58 | 59 | Or, you can start via ``cmake-gui`` if you prefer. Run ``Configure`` and select your desired build system. Then click ``Generate``. Then open the solution/project/makefile in your IDE of choice. 60 | 61 | ## Installation 62 | On macOS you can just copy the `HDRView.app` bundle to your `/Applications` folder. 63 | 64 | On Windows you'll need to copy `HDRView.exe` together with the accompanying `assets` folder to your desired installation location. 65 | 66 | Recent version of macOS will complain that the app is unsigned and from an unknown developer. You will need to go to the Security and Privacy part of system Settings to allow the app to run. 67 | 68 | ## Running from the terminal 69 | 70 | You can also run HDRView from the terminal. Run ``HDRView --help`` to see the command-line options. On macOS the executable is stored within the app bundle in `HDRView.app/Contents/MacOS/HDRView`. You might want to add it, or a symlink, to your path. 71 | 72 | ## License 73 | 74 | Copyright (c) Wojciech Jarosz 75 | 76 | 3-clause BSD. For details, see the ``LICENSE.txt`` file. 77 | 78 | HDRView builds on a number libraries. See the details in the Credits tab of the About dialog. 79 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | ## 4 | 5 | - [ ] sort image list 6 | - [ ] left vs. right align images in image list 7 | - [x] have spdlog output to the log window 8 | - [x] add image and channel list filtering 9 | - [x] load images asynchronously 10 | - [ ] load multiple images/entire directories 11 | - [x] drop event to load files for GLFW 12 | - [x] multi-select on desktop 13 | - [x] fix reference image index when closing image 14 | - [ ] render pixel grid in the shader 15 | - [ ] figure out how to better handle channels dropdown with channel groups and arbitrary channels 16 | - [ ] improve statistics 17 | - [x] compute min/max/avg ignoring infinities, and report # of infinities 18 | - [ ] account for blend mode when computing pixel hover (and statistics) 19 | - [x] figure our threading while considering emscripten 20 | - [ ] selection support 21 | - [ ] compute statistics also over selection 22 | - [ ] command palette/improved hotkey support 23 | - [ ] save as support for OpenEXR 24 | - [ ] handle multi-view EXRs better 25 | - [ ] pinch-zoom and pan support in emscripten with SDL 26 | - [ ] move shader, texture, renderpass and others into separate library 27 | - [ ] merge metal/glsl shaders + C++ colorspace stuff? 28 | - [ ] express app/viewport/image coordinate transforms using 2x2 matrices 29 | 30 | 31 | ## Old 32 | 33 | - [ ] Improve responsiveness during long operations 34 | - [x] Add progress bars 35 | - [x] Run them in a separate thread and avoid freezing the main application 36 | - [x] Send texture data to GL in smaller tiles, across several re-draws to avoid stalling main app 37 | - [ ] Allow canceling/aborting long operations 38 | - [ ] Improved DNG/demosaicing pipeline 39 | - [ ] Improve DNG color correction 40 | - [ ] Allow skipping DNG demosaicing during load 41 | - [ ] Add demosaicing/color correction/white balancing post-load filters 42 | - [ ] Will require storing DNG metadata to apply correct color-correction matrix 43 | - [x] Selection support 44 | - [ ] More image filters/transformations/adjustments 45 | - [x] Canvas size/cropping 46 | - [ ] White balance adjustment 47 | - [x] Brightness/contrast 48 | - [ ] Luminance/chromaticity denoising 49 | - [ ] Levels 50 | - [x] Hue/Saturation 51 | - [x] Convert to grayscale/desaturate 52 | - [x] Invert 53 | - [ ] Equalize/normalize histogram 54 | - [ ] Match color/histogram matching 55 | - [ ] FFT-based convolution/blur 56 | - [ ] Motion blur 57 | - [x] Merge down/flatten layers 58 | - [ ] Enable processing/filtering images passed on command-line even in GUI mode (e.g. load many images, blur them, and then display them in the GUI, possibly without saving) 59 | - [ ] HDR merging 60 | - [ ] HDR tonemapping 61 | - [x] General image editing 62 | - [x] Clone stamp 63 | - [x] Airbrush 64 | - [x] Cropping 65 | - [ ] GUI improvements 66 | - [x] Add support for resizing side panel 67 | - [ ] Allow error logging to output to a debug status panel in the GUI. 68 | - [x] Improved drop-down menus 69 | - [x] Save all 70 | - [x] Close all 71 | - [ ] Show command history and allow undoing/redoing multiple steps at once 72 | -------------------------------------------------------------------------------- /assets/app_settings/apple/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | ${HELLO_IMGUI_BUNDLE_IDENTIFIER} 7 | 8 | CFBundleName 9 | ${HELLO_IMGUI_BUNDLE_NAME} 10 | 11 | CFBundleDisplayName 12 | ${HELLO_IMGUI_ICON_DISPLAY_NAME} 13 | 14 | CFBundleExecutable 15 | ${HELLO_IMGUI_BUNDLE_EXECUTABLE} 16 | 17 | CFBundleShortVersionString 18 | ${HELLO_IMGUI_BUNDLE_VERSION} 19 | 20 | CFBundleVersion 21 | ${HELLO_IMGUI_BUNDLE_VERSION} 22 | 23 | NSHumanReadableCopyright 24 | ${HELLO_IMGUI_BUNDLE_COPYRIGHT} 25 | 26 | CFBundleIconFile 27 | ${HELLO_IMGUI_BUNDLE_ICON_FILE} 28 | 29 | NSBluetoothAlwaysUsageDescription 30 | Bluetooth is used (by the SDL Library) when using Bluetooth joysticks 31 | 32 | UIApplicationSupportsIndirectInputEvents 33 | 34 | 35 | NSHighResolutionCapable 36 | 37 | 38 | CFBundleDocumentTypes 39 | 40 | 41 | CFBundleTypeName 42 | Image file 43 | CFBundleTypeRole 44 | Viewer 45 | LSHandlerRank 46 | Default 47 | LSItemContentTypes 48 | 49 | public.image 50 | 51 | 52 | 53 | CFBundleTypeName 54 | Folders 55 | CFBundleTypeRole 56 | Viewer 57 | LSHandlerRank 58 | Alternate 59 | LSItemContentTypes 60 | 61 | public.folder 62 | 63 | 64 | 65 | 66 | NSPrincipalClass 67 | NSApplication 68 | 69 | NSSupportsAutomaticGraphicsSwitching 70 | 71 | 72 | CSResourcesFileMapped 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /assets/app_settings/emscripten/coi-serviceworker.js: -------------------------------------------------------------------------------- 1 | /*! coi-serviceworker v0.1.7 - Guido Zuidhof and contributors, licensed under MIT */ 2 | let coepCredentialless = false; 3 | if (typeof window === 'undefined') { 4 | self.addEventListener("install", () => self.skipWaiting()); 5 | self.addEventListener("activate", (event) => event.waitUntil(self.clients.claim())); 6 | 7 | self.addEventListener("message", (ev) => { 8 | if (!ev.data) { 9 | return; 10 | } else if (ev.data.type === "deregister") { 11 | self.registration 12 | .unregister() 13 | .then(() => { 14 | return self.clients.matchAll(); 15 | }) 16 | .then(clients => { 17 | clients.forEach((client) => client.navigate(client.url)); 18 | }); 19 | } else if (ev.data.type === "coepCredentialless") { 20 | coepCredentialless = ev.data.value; 21 | } 22 | }); 23 | 24 | self.addEventListener("fetch", function (event) { 25 | const r = event.request; 26 | if (r.cache === "only-if-cached" && r.mode !== "same-origin") { 27 | return; 28 | } 29 | 30 | const request = (coepCredentialless && r.mode === "no-cors") 31 | ? new Request(r, { 32 | credentials: "omit", 33 | }) 34 | : r; 35 | event.respondWith( 36 | fetch(request) 37 | .then((response) => { 38 | if (response.status === 0) { 39 | return response; 40 | } 41 | 42 | const newHeaders = new Headers(response.headers); 43 | newHeaders.set("Cross-Origin-Embedder-Policy", 44 | coepCredentialless ? "credentialless" : "require-corp" 45 | ); 46 | if (!coepCredentialless) { 47 | newHeaders.set("Cross-Origin-Resource-Policy", "cross-origin"); 48 | } 49 | newHeaders.set("Cross-Origin-Opener-Policy", "same-origin"); 50 | 51 | return new Response(response.body, { 52 | status: response.status, 53 | statusText: response.statusText, 54 | headers: newHeaders, 55 | }); 56 | }) 57 | .catch((e) => console.error(e)) 58 | ); 59 | }); 60 | 61 | } else { 62 | (() => { 63 | // You can customize the behavior of this script through a global `coi` variable. 64 | const coi = { 65 | shouldRegister: () => true, 66 | shouldDeregister: () => false, 67 | //coepCredentialless: () => (window.chrome !== undefined || window.netscape !== undefined), 68 | coepCredentialless: () => navigator.userAgentData?.brands?.some((b) => b.brand == "Chromium"), 69 | doReload: () => window.location.reload(), 70 | quiet: false, 71 | ...window.coi 72 | }; 73 | 74 | const n = navigator; 75 | 76 | if (n.serviceWorker && n.serviceWorker.controller) { 77 | n.serviceWorker.controller.postMessage({ 78 | type: "coepCredentialless", 79 | value: coi.coepCredentialless(), 80 | }); 81 | 82 | if (coi.shouldDeregister()) { 83 | n.serviceWorker.controller.postMessage({ type: "deregister" }); 84 | } 85 | } 86 | 87 | // If we're already coi: do nothing. Perhaps it's due to this script doing its job, or COOP/COEP are 88 | // already set from the origin server. Also if the browser has no notion of crossOriginIsolated, just give up here. 89 | if (window.crossOriginIsolated !== false || !coi.shouldRegister()) return; 90 | 91 | if (!window.isSecureContext) { 92 | !coi.quiet && console.log("COOP/COEP Service Worker not registered, a secure context is required."); 93 | return; 94 | } 95 | 96 | // In some environments (e.g. Chrome incognito mode) this won't be available 97 | if (n.serviceWorker) { 98 | n.serviceWorker.register(window.document.currentScript.src).then( 99 | (registration) => { 100 | !coi.quiet && console.log("COOP/COEP Service Worker registered", registration.scope); 101 | 102 | registration.addEventListener("updatefound", () => { 103 | !coi.quiet && console.log("Reloading page to make use of updated COOP/COEP Service Worker."); 104 | coi.doReload(); 105 | }); 106 | 107 | // If the registration is active, but it's not controlling the page 108 | if (registration.active && !n.serviceWorker.controller) { 109 | !coi.quiet && console.log("Reloading page to make use of COOP/COEP Service Worker."); 110 | coi.doReload(); 111 | } 112 | }, 113 | (err) => { 114 | !coi.quiet && console.error("COOP/COEP Service Worker failed to register:", err); 115 | } 116 | ); 117 | } 118 | })(); 119 | } 120 | -------------------------------------------------------------------------------- /assets/app_settings/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkjarosz/hdrview/5ef1c6258de36bead0aee8919e6748eb9fbe7829/assets/app_settings/icon.png -------------------------------------------------------------------------------- /assets/fonts/MaterialIcons-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkjarosz/hdrview/5ef1c6258de36bead0aee8919e6748eb9fbe7829/assets/fonts/MaterialIcons-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/MaterialSymbolsRounded_Filled-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkjarosz/hdrview/5ef1c6258de36bead0aee8919e6748eb9fbe7829/assets/fonts/MaterialSymbolsRounded_Filled-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/Roboto/Roboto-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkjarosz/hdrview/5ef1c6258de36bead0aee8919e6748eb9fbe7829/assets/fonts/Roboto/Roboto-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/Roboto/Roboto-BoldItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkjarosz/hdrview/5ef1c6258de36bead0aee8919e6748eb9fbe7829/assets/fonts/Roboto/Roboto-BoldItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/Roboto/Roboto-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkjarosz/hdrview/5ef1c6258de36bead0aee8919e6748eb9fbe7829/assets/fonts/Roboto/Roboto-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/Roboto/Roboto-RegularItalic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkjarosz/hdrview/5ef1c6258de36bead0aee8919e6748eb9fbe7829/assets/fonts/Roboto/Roboto-RegularItalic.ttf -------------------------------------------------------------------------------- /assets/fonts/Roboto/RobotoMono-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkjarosz/hdrview/5ef1c6258de36bead0aee8919e6748eb9fbe7829/assets/fonts/Roboto/RobotoMono-Bold.ttf -------------------------------------------------------------------------------- /assets/fonts/Roboto/RobotoMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkjarosz/hdrview/5ef1c6258de36bead0aee8919e6748eb9fbe7829/assets/fonts/Roboto/RobotoMono-Regular.ttf -------------------------------------------------------------------------------- /assets/fonts/lucide.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkjarosz/hdrview/5ef1c6258de36bead0aee8919e6748eb9fbe7829/assets/fonts/lucide.ttf -------------------------------------------------------------------------------- /assets/fonts/materialdesignicons-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkjarosz/hdrview/5ef1c6258de36bead0aee8919e6748eb9fbe7829/assets/fonts/materialdesignicons-webfont.ttf -------------------------------------------------------------------------------- /assets/shaders/colorspaces.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | #ifndef saturate 4 | #define saturate(v) clamp(v, 0.0, 1.0) 5 | #endif 6 | 7 | // Luminance-chroma to RGB conversion 8 | // assumes `input: {RY, Y, BY}` 9 | vec3 YCToRGB(vec3 c, vec3 Yw) 10 | { 11 | if (c[0] == 0.0 && c[2] == 0.0) 12 | // 13 | // Special case -- both chroma channels are 0. To avoid 14 | // rounding errors, we explicitly set the output R, G and B 15 | // channels equal to the input luminance. 16 | // 17 | return vec3(c.g, c.g, c.g); 18 | 19 | float Y = c.g; 20 | float r = (c.r + 1.0) * c.g; 21 | float b = (c.b + 1.0) * c.g; 22 | float g = (Y - r * Yw.x - b * Yw.z) / Yw.y; 23 | 24 | return vec3(r, g, b); 25 | } 26 | 27 | float linearToS(float a) 28 | { 29 | float old_sign = sign(a); 30 | a = abs(a); 31 | return a < 0.0031308 ? old_sign * 12.92 * a : old_sign * 1.055 * pow(a, 1.0 / 2.4) - 0.055; 32 | } 33 | 34 | vec3 linearToSRGB(vec3 color) { return vec3(linearToS(color.r), linearToS(color.g), linearToS(color.b)); } 35 | vec4 linearToSRGB(vec4 color) { return vec4(linearToSRGB(color.rgb), color.a); } 36 | 37 | float sToLinear(float a) 38 | { 39 | float old_sign = sign(a); 40 | a = abs(a); 41 | return a < 0.04045 ? old_sign * (1.0 / 12.92) * a : old_sign * pow((a + 0.055) * (1.0 / 1.055), 2.4); 42 | } 43 | 44 | vec3 sRGBToLinear(vec3 color) { return vec3(sToLinear(color.r), sToLinear(color.g), sToLinear(color.b)); } 45 | vec4 sRGBToLinear(vec4 color) { return vec4(sRGBToLinear(color.rgb), color.a); } 46 | 47 | // returns the luminance of a linear rgb color 48 | float RGBToY(vec3 rgb, vec3 weights) { return dot(weights, rgb); } 49 | 50 | // Converts a color from linear RGB to XYZ space 51 | vec3 RGBToXYZ(vec3 rgb) 52 | { 53 | const mat3 RGB2XYZ = mat3(0.412453, 0.212671, 0.019334, 0.357580, 0.715160, 0.119193, 0.180423, 0.072169, 0.950227); 54 | return RGB2XYZ * rgb; 55 | } 56 | 57 | // Converts a color from XYZ to linear RGB space 58 | vec3 XYZToRGB(vec3 xyz) 59 | { 60 | const mat3 XYZ2RGB = 61 | mat3(3.240479, -0.969256, 0.055648, -1.537150, 1.875992, -0.204043, -0.498535, 0.041556, 1.057311); 62 | return XYZ2RGB * xyz; 63 | } 64 | -------------------------------------------------------------------------------- /assets/shaders/colorspaces.metal: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace metal; 4 | 5 | 6 | // Luminance-chroma to RGB conversion 7 | // assumes `input: {RY, Y, BY}` 8 | float3 YCToRGB(float3 input, float3 Yw) 9 | { 10 | if (input[0] == 0.0 && input[2] == 0.0) 11 | // 12 | // Special case -- both chroma channels are 0. To avoid 13 | // rounding errors, we explicitly set the output R, G and B 14 | // channels equal to the input luminance. 15 | // 16 | return float3(input.g, input.g, input.g); 17 | 18 | float Y = input.g; 19 | float r = (input.r + 1.0) * input.g; 20 | float b = (input.b + 1.0) * input.g; 21 | float g = (Y - r * Yw.x - b * Yw.z) / Yw.y; 22 | 23 | return float3(r, g, b); 24 | } 25 | 26 | float linearToS(float a) 27 | { 28 | float old_sign = sign(a); 29 | a = fabs(a); 30 | return a < 0.0031308f ? old_sign * 12.92f * a : old_sign * 1.055f * pow(a, 1.0f / 2.4f) - 0.055f; 31 | } 32 | 33 | float3 linearToSRGB(float3 color) { return float3(linearToS(color.r), linearToS(color.g), linearToS(color.b)); } 34 | float4 linearToSRGB(float4 color) { return float4(linearToSRGB(color.rgb), color.a); } 35 | 36 | float sToLinear(float a) 37 | { 38 | float old_sign = sign(a); 39 | a = fabs(a); 40 | return a < 0.04045f ? old_sign * (1.0f / 12.92f) * a : old_sign * pow((a + 0.055f) * (1.0f / 1.055f), 2.4f); 41 | } 42 | 43 | float3 sRGBToLinear(float3 color) { return float3(sToLinear(color.r), sToLinear(color.g), sToLinear(color.b)); } 44 | float4 sRGBToLinear(float4 color) { return float4(sRGBToLinear(color.rgb), color.a); } 45 | 46 | // returns the luminance of a linear rgb color 47 | float RGBToY(float3 rgb, float3 weights) 48 | { 49 | return dot(weights, rgb); 50 | } 51 | 52 | // Converts a color from linear RGB to XYZ space 53 | float3 RGBToXYZ(float3 rgb) 54 | { 55 | const float3x3 RGB2XYZ = 56 | float3x3(0.412453, 0.212671, 0.019334, 0.357580, 0.715160, 0.119193, 0.180423, 0.072169, 0.950227); 57 | return RGB2XYZ * rgb; 58 | } 59 | 60 | // Converts a color from XYZ to linear RGB space 61 | float3 XYZToRGB(float3 xyz) 62 | { 63 | const float3x3 XYZ2RGB = 64 | float3x3(3.240479, -0.969256, 0.055648, -1.537150, 1.875992, -0.204043, -0.498535, 0.041556, 1.057311); 65 | return XYZ2RGB * xyz; 66 | } -------------------------------------------------------------------------------- /assets/shaders/image-shader_frag.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | 3 | // These need to stay in sync with the various enums in fwd.h 4 | #define CHANNEL_RGB 0 5 | #define CHANNEL_RED 1 6 | #define CHANNEL_GREEN 2 7 | #define CHANNEL_BLUE 3 8 | #define CHANNEL_ALPHA 4 9 | #define CHANNEL_Y 5 10 | 11 | #define Tonemap_sRGB 0 12 | #define Tonemap_Gamma 1 13 | #define Tonemap_FalseColor 2 14 | #define Tonemap_PositiveNegative 3 15 | 16 | #define NORMAL_BLEND 0 17 | #define MULTIPLY_BLEND 1 18 | #define DIVIDE_BLEND 2 19 | #define ADD_BLEND 3 20 | #define AVERAGE_BLEND 4 21 | #define SUBTRACT_BLEND 5 22 | #define DIFFERENCE_BLEND 6 23 | #define RELATIVE_DIFFERENCE_BLEND 7 24 | 25 | #define BG_BLACK 0 26 | #define BG_WHITE 1 27 | #define BG_DARK_CHECKER 2 28 | #define BG_LIGHT_CHECKER 3 29 | #define BG_CUSTOM_COLOR 4 30 | 31 | #define RGBA_Channels 0 32 | #define RGB_Channels 1 33 | #define XYZA_Channels 2 34 | #define XYZ_Channels 3 35 | #define YCA_Channels 4 36 | #define YC_Channels 5 37 | #define YA_Channels 6 38 | #define UVorXY_Channels 7 39 | #define Z_Channel 8 40 | #define Single_Channel 9 41 | 42 | in vec2 primary_uv; 43 | in vec2 secondary_uv; 44 | // in highp in vec4 gl_FragCoord; 45 | 46 | uniform bool has_reference; 47 | uniform bool do_dither; 48 | uniform vec2 randomness; 49 | uniform int blend_mode; 50 | uniform int channel; 51 | uniform float gain; 52 | uniform float gamma; 53 | uniform int tonemap_mode; 54 | uniform bool clamp_to_LDR; 55 | uniform int bg_mode; 56 | uniform vec4 bg_color; 57 | 58 | uniform sampler2D colormap; 59 | 60 | uniform sampler2D dither_texture; 61 | 62 | uniform sampler2D primary_0_texture; 63 | uniform sampler2D primary_1_texture; 64 | uniform sampler2D primary_2_texture; 65 | uniform sampler2D primary_3_texture; 66 | uniform vec3 primary_yw; 67 | uniform int primary_channels_type; 68 | uniform mat4 primary_M_to_Rec709; 69 | 70 | uniform sampler2D secondary_0_texture; 71 | uniform sampler2D secondary_1_texture; 72 | uniform sampler2D secondary_2_texture; 73 | uniform sampler2D secondary_3_texture; 74 | uniform vec3 secondary_yw; 75 | uniform int secondary_channels_type; 76 | uniform mat4 secondary_M_to_Rec709; 77 | 78 | uniform float time; 79 | uniform bool draw_clip_warnings; 80 | uniform vec2 clip_range; 81 | 82 | out vec4 frag_color; 83 | 84 | vec4 tonemap(vec4 color) 85 | { 86 | switch (tonemap_mode) 87 | { 88 | default: return color; 89 | case Tonemap_Gamma: return vec4(sRGBToLinear(sign(color.rgb) * pow(abs(color.rgb), vec3(1.0 / gamma))), color.a); 90 | case Tonemap_FalseColor: 91 | { 92 | float cmap_size = float(textureSize(colormap, 0).x); 93 | float t = mix(0.5 / cmap_size, (cmap_size - 0.5) / cmap_size, dot(color.rgb, vec3(1.0 / 3.0))); 94 | return vec4(sRGBToLinear(texture(colormap, vec2(t, 0.5)).rgb) * color.a, color.a); 95 | } 96 | case Tonemap_PositiveNegative: 97 | { 98 | float cmap_size = float(textureSize(colormap, 0).x); 99 | float t = mix(0.5 / cmap_size, (cmap_size - 0.5) / cmap_size, 0.5 * dot(color.rgb, vec3(1.0 / 3.0)) + 0.5); 100 | return vec4(sRGBToLinear(texture(colormap, vec2(t, 0.5)).rgb) * color.a, color.a); 101 | } 102 | } 103 | } 104 | 105 | float rand_box(vec2 xy) 106 | { 107 | // Result is in range (-0.5, 0.5) 108 | return (texture(dither_texture, xy / vec2(256, 256)).r + 0.5) / 65536.0 - 0.5; 109 | } 110 | 111 | float rand_tent(vec2 xy) 112 | { 113 | float r = rand_box(xy); 114 | 115 | // Convert uniform distribution into triangle-shaped distribution 116 | // Result is in range (-0.5,0.5) 117 | float rp = sqrt(2.0 * r); // positive triangle 118 | float rn = sqrt(2.0 * r + 1.0) - 1.0; // negative triangle 119 | return (r < 0.0) ? 0.5 * rn : 0.5 * rp; 120 | } 121 | 122 | vec4 choose_channel(vec4 rgba) 123 | { 124 | switch (channel) 125 | { 126 | case CHANNEL_RGB: return rgba; 127 | case CHANNEL_RED: return vec4(rgba.rrr, 1.0); 128 | case CHANNEL_GREEN: return vec4(rgba.ggg, 1.0); 129 | case CHANNEL_BLUE: return vec4(rgba.bbb, 1.0); 130 | case CHANNEL_ALPHA: return vec4(rgba.aaa, 1.0); 131 | case CHANNEL_Y: return vec4(vec3(dot(rgba.rgb, primary_yw)), rgba.a); 132 | } 133 | return rgba; 134 | } 135 | 136 | vec4 blend(vec4 top, vec4 bottom) 137 | { 138 | vec3 diff = top.rgb - bottom.rgb; 139 | float alpha = top.a + bottom.a * (1.0 - top.a); 140 | switch (blend_mode) 141 | { 142 | case NORMAL_BLEND: return vec4(top.rgb + bottom.rgb * (1.0 - top.a), alpha); 143 | case MULTIPLY_BLEND: return vec4(top.rgb * bottom.rgb, alpha); 144 | case DIVIDE_BLEND: return vec4(top.rgb / bottom.rgb, alpha); 145 | case ADD_BLEND: return vec4(top.rgb + bottom.rgb, alpha); 146 | case AVERAGE_BLEND: return 0.5 * (top + bottom); 147 | case SUBTRACT_BLEND: return vec4(diff, alpha); 148 | case DIFFERENCE_BLEND: return vec4(abs(diff), alpha); 149 | case RELATIVE_DIFFERENCE_BLEND: return vec4(abs(diff) / (bottom.rgb + vec3(0.01)), alpha); 150 | } 151 | return vec4(0.0); 152 | } 153 | 154 | vec4 dither(vec4 color) 155 | { 156 | if (!do_dither) 157 | return color; 158 | 159 | return color + vec4(vec3(rand_tent(gl_FragCoord.xy + randomness) / 256.0), 0.0); 160 | } 161 | 162 | float sample_channel(sampler2D sampler, vec2 uv, bool within_image) 163 | { 164 | return within_image ? texture(sampler, uv).r : 0.0; 165 | } 166 | 167 | void main() 168 | { 169 | vec2 pixel = vec2(gl_FragCoord.x, -gl_FragCoord.y); 170 | vec4 background = vec4(bg_color.rgb, 1.0); 171 | if (bg_mode == BG_BLACK) 172 | background.rgb = vec3(0.0); 173 | else if (bg_mode == BG_WHITE) 174 | background.rgb = vec3(1.0); 175 | else if (bg_mode == BG_DARK_CHECKER || bg_mode == BG_LIGHT_CHECKER) 176 | { 177 | float dark_gray = (bg_mode == BG_DARK_CHECKER) ? 0.1 : 0.5; 178 | float light_gray = (bg_mode == BG_DARK_CHECKER) ? 0.2 : 0.55; 179 | float checkerboard = mod(floor(pixel.x / 8.0) + floor(pixel.y / 8.0), 2.0) == 0.0 ? dark_gray : light_gray; 180 | background.rgb = sRGBToLinear(vec3(checkerboard)); 181 | } 182 | 183 | float zebra1 = (mod(float(int(floor((pixel.x + pixel.y - 30.0 * time) / 8.0))), 2.0) == 0.0) ? 0.0 : 1.0; 184 | float zebra2 = (mod(float(int(floor((pixel.x - pixel.y - 30.0 * time) / 8.0))), 2.0) == 0.0) ? 0.0 : 1.0; 185 | 186 | bool in_img = primary_uv.x < 1.0 && primary_uv.y < 1.0 && primary_uv.x > 0.0 && primary_uv.y > 0.0; 187 | bool in_ref = secondary_uv.x < 1.0 && secondary_uv.y < 1.0 && secondary_uv.x > 0.0 && secondary_uv.y > 0.0; 188 | 189 | if (!in_img && !(in_ref && has_reference)) 190 | { 191 | frag_color = linearToSRGB(background); 192 | return; 193 | } 194 | 195 | vec4 value = vec4( 196 | sample_channel(primary_0_texture, primary_uv, in_img), sample_channel(primary_1_texture, primary_uv, in_img), 197 | sample_channel(primary_2_texture, primary_uv, in_img), sample_channel(primary_3_texture, primary_uv, in_img)); 198 | 199 | if (primary_channels_type == YCA_Channels || primary_channels_type == YC_Channels) 200 | value.xyz = YCToRGB(value.xyz, primary_yw); 201 | 202 | value = primary_M_to_Rec709 * value; 203 | 204 | if (has_reference) 205 | { 206 | vec4 reference_val = vec4(sample_channel(secondary_0_texture, secondary_uv, in_ref), 207 | sample_channel(secondary_1_texture, secondary_uv, in_ref), 208 | sample_channel(secondary_2_texture, secondary_uv, in_ref), 209 | sample_channel(secondary_3_texture, secondary_uv, in_ref)); 210 | 211 | if (secondary_channels_type == YCA_Channels || secondary_channels_type == YC_Channels) 212 | reference_val.xyz = YCToRGB(reference_val.xyz, secondary_yw); 213 | 214 | reference_val = secondary_M_to_Rec709 * reference_val; 215 | 216 | value = blend(value, reference_val); 217 | } 218 | 219 | vec4 foreground = choose_channel(value) * vec4(vec3(gain), 1.0); 220 | vec4 tonemapped = tonemap(foreground) + background * (1.0 - foreground.a); 221 | bvec3 clipped = greaterThan(foreground.rgb, clip_range.yyy); 222 | bvec3 crushed = lessThan(foreground.rgb, clip_range.xxx); 223 | vec4 blended = linearToSRGB(tonemapped); 224 | vec4 dithered = dither(blended); 225 | dithered = clamp(dithered, clamp_to_LDR ? 0.0 : -64.0, clamp_to_LDR ? 1.0 : 64.0); 226 | if (draw_clip_warnings) 227 | { 228 | dithered.rgb = mix(dithered.rgb, vec3(zebra1), clipped); 229 | dithered.rgb = mix(dithered.rgb, vec3(zebra2), crushed); 230 | } 231 | frag_color = vec4(dithered.rgb, 1.0); 232 | } -------------------------------------------------------------------------------- /assets/shaders/image-shader_vert.glsl: -------------------------------------------------------------------------------- 1 | precision mediump float; 2 | in vec2 position; 3 | 4 | out vec2 primary_uv; 5 | out vec2 secondary_uv; 6 | 7 | uniform vec2 primary_scale; 8 | uniform vec2 primary_pos; 9 | uniform vec2 secondary_scale; 10 | uniform vec2 secondary_pos; 11 | 12 | void main() 13 | { 14 | primary_uv = (position / 2.0 - primary_pos + 0.5) / primary_scale; 15 | secondary_uv = (position / 2.0 - secondary_pos + 0.5) / secondary_scale; 16 | gl_Position = vec4(position.x, -position.y, 0.0, 1.0); 17 | } -------------------------------------------------------------------------------- /assets/shaders/image-shader_vert.metal: -------------------------------------------------------------------------------- 1 | using namespace metal; 2 | 3 | struct VertexOut 4 | { 5 | float4 position [[position]]; 6 | float2 primary_uv; 7 | float2 secondary_uv; 8 | }; 9 | 10 | vertex VertexOut vertex_main(const device float2 *position, 11 | constant float2 &primary_scale, 12 | constant float2 &primary_pos, 13 | constant float2 &secondary_scale, 14 | constant float2 &secondary_pos, 15 | uint id [[vertex_id]]) { 16 | VertexOut vert = {}; 17 | vert.primary_uv = (((position[id] / float2(2.0)) - primary_pos) + float2(0.5)) / primary_scale; 18 | vert.secondary_uv = (((position[id] / float2(2.0)) - secondary_pos) + float2(0.5)) / secondary_scale; 19 | vert.position = float4(position[id].x, -position[id].y, 0.0, 1.0); 20 | return vert; 21 | } -------------------------------------------------------------------------------- /cmake/CPM.cmake: -------------------------------------------------------------------------------- 1 | set(CPM_DOWNLOAD_VERSION 0.40.2) 2 | 3 | if(CPM_SOURCE_CACHE) 4 | # Expand relative path. This is important if the provided path contains a tilde (~) 5 | get_filename_component(CPM_SOURCE_CACHE ${CPM_SOURCE_CACHE} ABSOLUTE) 6 | set(CPM_DOWNLOAD_LOCATION "${CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 7 | elseif(DEFINED ENV{CPM_SOURCE_CACHE}) 8 | set(CPM_DOWNLOAD_LOCATION "$ENV{CPM_SOURCE_CACHE}/cpm/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 9 | else() 10 | set(CPM_DOWNLOAD_LOCATION "${CMAKE_BINARY_DIR}/cmake/CPM_${CPM_DOWNLOAD_VERSION}.cmake") 11 | endif() 12 | 13 | if(NOT (EXISTS ${CPM_DOWNLOAD_LOCATION})) 14 | message(STATUS "Downloading CPM.cmake to ${CPM_DOWNLOAD_LOCATION}") 15 | file(DOWNLOAD https://github.com/cpm-cmake/CPM.cmake/releases/download/v${CPM_DOWNLOAD_VERSION}/CPM.cmake 16 | ${CPM_DOWNLOAD_LOCATION} 17 | ) 18 | endif() 19 | 20 | include(${CPM_DOWNLOAD_LOCATION}) 21 | -------------------------------------------------------------------------------- /cmake/VersionFromGit.cmake: -------------------------------------------------------------------------------- 1 | # The MIT License (MIT) 2 | # 3 | # Copyright (c) 2016-2017 Theo Willows 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | 23 | cmake_minimum_required(VERSION 3.13) 24 | 25 | include( CMakeParseArguments ) 26 | 27 | function( version_from_git ) 28 | # Parse arguments 29 | set( options OPTIONAL FAST ) 30 | set( oneValueArgs 31 | GIT_EXECUTABLE 32 | INCLUDE_HASH 33 | LOG 34 | TIMESTAMP 35 | ) 36 | set( multiValueArgs ) 37 | cmake_parse_arguments( ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} ) 38 | 39 | # Defaults 40 | if( NOT DEFINED ARG_INCLUDE_HASH ) 41 | set( ARG_INCLUDE_HASH ON ) 42 | endif() 43 | 44 | if( DEFINED ARG_GIT_EXECUTABLE ) 45 | set( GIT_EXECUTABLE "${ARG_GIT_EXECUTABLE}" ) 46 | else () 47 | # Find Git or bail out 48 | find_package( Git ) 49 | if( NOT GIT_FOUND ) 50 | message( FATAL_ERROR "[VersionFromGit] Git not found" ) 51 | endif( NOT GIT_FOUND ) 52 | endif() 53 | 54 | # Git describe 55 | execute_process( 56 | COMMAND "${GIT_EXECUTABLE}" describe --tags --dirty 57 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 58 | RESULT_VARIABLE git_result 59 | OUTPUT_VARIABLE git_describe 60 | ERROR_VARIABLE git_error 61 | OUTPUT_STRIP_TRAILING_WHITESPACE 62 | ERROR_STRIP_TRAILING_WHITESPACE 63 | ) 64 | if( NOT git_result EQUAL 0 ) 65 | message( WARNING 66 | "[VersionFromGit] Failed to execute Git: ${git_error}" 67 | ) 68 | set( git_describe "" ) 69 | endif() 70 | 71 | # Get Git tag 72 | execute_process( 73 | COMMAND "${GIT_EXECUTABLE}" describe --tags --abbrev=0 74 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 75 | RESULT_VARIABLE git_result 76 | OUTPUT_VARIABLE git_tag 77 | ERROR_VARIABLE git_error 78 | OUTPUT_STRIP_TRAILING_WHITESPACE 79 | ERROR_STRIP_TRAILING_WHITESPACE 80 | ) 81 | if( NOT git_result EQUAL 0 ) 82 | message( WARNING 83 | "[VersionFromGit] Failed to execute Git: ${git_error}" 84 | ) 85 | set( git_tag "" ) 86 | endif() 87 | 88 | if( git_tag MATCHES "^v(0|[1-9][0-9]*)[.](0|[1-9][0-9]*)[.](0|[1-9][0-9]*)(-[.0-9A-Za-z-]+)?([+][.0-9A-Za-z-]+)?$" ) 89 | set( version_major "${CMAKE_MATCH_1}" ) 90 | set( version_minor "${CMAKE_MATCH_2}" ) 91 | set( version_patch "${CMAKE_MATCH_3}" ) 92 | set( identifiers "${CMAKE_MATCH_4}" ) 93 | set( metadata "${CMAKE_MATCH_5}" ) 94 | else() 95 | message( WARNING 96 | "[VersionFromGit] Git tag isn't valid semantic version: [${git_tag}]" 97 | ) 98 | set( version_major "0" ) 99 | set( version_minor "0" ) 100 | set( version_patch "0" ) 101 | set( identifiers "" ) 102 | set( metadata "" ) 103 | endif() 104 | 105 | if( "${git_tag}" STREQUAL "${git_describe}" ) 106 | set( git_at_a_tag ON ) 107 | endif() 108 | 109 | if( NOT git_at_a_tag ) 110 | # Extract the Git hash (if one exists) 111 | string( REGEX MATCH "g[0-9a-f]+$" git_hash "${git_describe}" ) 112 | endif() 113 | 114 | # Construct the version variables 115 | set( version ${version_major}.${version_minor}.${version_patch} ) 116 | set( semver ${version} ) 117 | 118 | # Identifiers 119 | if( identifiers MATCHES ".+" ) 120 | string( SUBSTRING "${identifiers}" 1 -1 identifiers ) 121 | set( semver "${semver}-${identifiers}") 122 | endif() 123 | 124 | # Metadata 125 | # TODO Split and join (add Git hash inbetween) 126 | if( metadata MATCHES ".+" ) 127 | string( SUBSTRING "${metadata}" 1 -1 metadata ) 128 | # Split 129 | string( REPLACE "." ";" metadata "${metadata}" ) 130 | endif() 131 | 132 | if( NOT git_at_a_tag ) 133 | 134 | if( ARG_INCLUDE_HASH ) 135 | list( APPEND metadata "${git_hash}" ) 136 | endif( ARG_INCLUDE_HASH ) 137 | 138 | # Timestamp 139 | if( DEFINED ARG_TIMESTAMP ) 140 | string( TIMESTAMP timestamp "${ARG_TIMESTAMP}" UTC ) 141 | list( APPEND metadata "${timestamp}" ) 142 | endif( DEFINED ARG_TIMESTAMP ) 143 | 144 | endif() 145 | 146 | # Join 147 | string( REPLACE ";" "." metadata "${metadata}" ) 148 | 149 | if( metadata MATCHES ".+" ) 150 | set( semver "${semver}+${metadata}") 151 | endif() 152 | 153 | # Log the results 154 | if( ARG_LOG ) 155 | message( STATUS 156 | "Version: ${version} 157 | Git tag: [${git_tag}] 158 | Git hash: [${git_hash}] 159 | Decorated: [${git_describe}] 160 | Identifiers: [${identifiers}] 161 | Metadata: [${metadata}] 162 | SemVer: [${semver}]" 163 | ) 164 | endif( ARG_LOG ) 165 | 166 | # Set parent scope variables 167 | set( GIT_TAG ${git_tag} PARENT_SCOPE ) 168 | set( GIT_HASH ${git_hash} PARENT_SCOPE ) 169 | set( GIT_DESCRIBE ${git_describe} PARENT_SCOPE ) 170 | set( SEMVER ${semver} PARENT_SCOPE ) 171 | set( VERSION ${version} PARENT_SCOPE ) 172 | set( VERSION_MAJOR ${version_major} PARENT_SCOPE ) 173 | set( VERSION_MINOR ${version_minor} PARENT_SCOPE ) 174 | set( VERSION_PATCH ${version_patch} PARENT_SCOPE ) 175 | 176 | endfunction( version_from_git ) 177 | -------------------------------------------------------------------------------- /cmake/generate_version.cmake: -------------------------------------------------------------------------------- 1 | # Generates version.cpp with git information at during build (and not just configure) 2 | # 3 | # Based on: 4 | # 5 | # https://mind.be/compile-time-git-version-info-using-cmake/ 6 | # 7 | # and 8 | # 9 | # https://www.mattkeeter.com/blog/2018-01-06-versioning/ 10 | # 11 | 12 | # Find Git or bail out 13 | find_package(Git) 14 | if(NOT GIT_FOUND) 15 | message(FATAL_ERROR "[VersionFromGit] Git not found") 16 | endif(NOT GIT_FOUND) 17 | 18 | # Git describe 19 | execute_process( 20 | COMMAND "${GIT_EXECUTABLE}" describe --tags --dirty 21 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 22 | RESULT_VARIABLE git_result 23 | OUTPUT_VARIABLE git_describe 24 | ERROR_VARIABLE git_error 25 | OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_STRIP_TRAILING_WHITESPACE 26 | ) 27 | if(NOT git_result EQUAL 0) 28 | message(WARNING "[VersionFromGit] Failed to execute Git: ${git_error}") 29 | set(git_describe "") 30 | endif() 31 | 32 | # Get Git tag 33 | execute_process( 34 | COMMAND "${GIT_EXECUTABLE}" describe --tags --abbrev=0 35 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 36 | RESULT_VARIABLE git_result 37 | OUTPUT_VARIABLE git_tag 38 | ERROR_VARIABLE git_error 39 | OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_STRIP_TRAILING_WHITESPACE 40 | ) 41 | if(NOT git_result EQUAL 0) 42 | message(WARNING "[VersionFromGit] Failed to execute Git: ${git_error}") 43 | set(git_tag "") 44 | endif() 45 | 46 | if(git_tag MATCHES "^v(0|[1-9][0-9]*)[.](0|[1-9][0-9]*)[.](0|[1-9][0-9]*)(-[.0-9A-Za-z-]+)?([+][.0-9A-Za-z-]+)?$") 47 | set(version_major "${CMAKE_MATCH_1}") 48 | set(version_minor "${CMAKE_MATCH_2}") 49 | set(version_patch "${CMAKE_MATCH_3}") 50 | set(identifiers "${CMAKE_MATCH_4}") 51 | set(metadata "${CMAKE_MATCH_5}") 52 | else() 53 | message(WARNING "[VersionFromGit] Git tag isn't valid semantic version: [${git_tag}]") 54 | set(version_major "0") 55 | set(version_minor "0") 56 | set(version_patch "0") 57 | set(identifiers "") 58 | set(metadata "") 59 | endif() 60 | 61 | if("${git_tag}" STREQUAL "${git_describe}") 62 | set(git_at_a_tag ON) 63 | endif() 64 | 65 | if(NOT git_at_a_tag) 66 | # Extract the Git hash (if one exists) 67 | string(REGEX MATCH "g[0-9a-f]+$" git_hash "${git_describe}") 68 | endif() 69 | 70 | # Construct the version variables 71 | set(version ${version_major}.${version_minor}.${version_patch}) 72 | set(semver ${version}) 73 | 74 | # Identifiers 75 | if(identifiers MATCHES ".+") 76 | string(SUBSTRING "${identifiers}" 1 -1 identifiers) 77 | set(semver "${semver}-${identifiers}") 78 | endif() 79 | 80 | # Metadata TODO Split and join (add Git hash inbetween) 81 | if(metadata MATCHES ".+") 82 | string(SUBSTRING "${metadata}" 1 -1 metadata) 83 | # Split 84 | string(REPLACE "." ";" metadata "${metadata}") 85 | endif() 86 | 87 | if(NOT git_at_a_tag) 88 | 89 | list(APPEND metadata "${git_hash}") 90 | 91 | # Timestamp 92 | if(DEFINED ARG_TIMESTAMP) 93 | string( 94 | TIMESTAMP 95 | timestamp "${ARG_TIMESTAMP}" 96 | UTC 97 | ) 98 | list(APPEND metadata "${timestamp}") 99 | endif(DEFINED ARG_TIMESTAMP) 100 | 101 | endif() 102 | 103 | # Join 104 | string(REPLACE ";" "." metadata "${metadata}") 105 | 106 | if(metadata MATCHES ".+") 107 | set(semver "${semver}+${metadata}") 108 | endif() 109 | 110 | # Log the results 111 | message( 112 | STATUS 113 | "Version: ${version} 114 | Git tag: [${git_tag}] 115 | Git hash: [${git_hash}] 116 | Decorated: [${git_describe}] 117 | Identifiers: [${identifiers}] 118 | Metadata: [${metadata}] 119 | SemVer: [${semver}]" 120 | ) 121 | 122 | # Set variables 123 | set(GIT_TAG ${git_tag}) 124 | set(GIT_HASH ${git_hash}) 125 | set(GIT_DESCRIBE ${git_describe}) 126 | set(SEMVER ${semver}) 127 | set(VERSION ${version}) 128 | set(VERSION_MAJOR ${version_major}) 129 | set(VERSION_MINOR ${version_minor}) 130 | set(VERSION_PATCH ${version_patch}) 131 | 132 | string(TIMESTAMP BUILD_TIME "%Y-%m-%d %H:%M") 133 | message(STATUS "Saving build timestamp: ${BUILD_TIME}") 134 | 135 | # Multiply CMAKE_SIZEOF_VOID_P by 8 to get the bitness 136 | math(EXPR BITNESS "${CMAKE_SIZEOF_VOID_P} * 8") 137 | set(VERSION_LONG "${GIT_DESCRIBE} (${BITNESS} bit)") 138 | 139 | configure_file("${SRC_DIR}/version.cpp.in" "${BIN_DIR}/version.cpp" @ONLY) 140 | -------------------------------------------------------------------------------- /cmake/modules/FindJXL.cmake: -------------------------------------------------------------------------------- 1 | # cmake-format: off 2 | # 3 | # Module to find libjxl 4 | # 5 | # This modules defines the following variables: 6 | # - JXL_FOUND True if libjxl was found. 7 | # - JXL_INCLUDES Directory to include for libjxl headers 8 | # - JXL_LIBRARIES Libraries to link to 9 | # 10 | # If found, it will also define imported targets for the libraries: 11 | # - jxl 12 | # - jxl_threads 13 | # - jxl_cms 14 | # 15 | # cmake-format: on 16 | 17 | include(FindPackageHandleStandardArgs) 18 | 19 | find_path(JXL_INCLUDE_DIR NAMES jxl/decode.h jxl/encode.h) 20 | mark_as_advanced(JXL_INCLUDE_DIR) 21 | 22 | if(JXL_INCLUDE_DIR) 23 | file(STRINGS "${JXL_INCLUDE_DIR}/jxl/version.h" TMP REGEX "^#define JPEGXL_MAJOR_VERSION .*$") 24 | string(REGEX MATCHALL "[0-9]+" JPEGXL_MAJOR_VERSION ${TMP}) 25 | file(STRINGS "${JXL_INCLUDE_DIR}/jxl/version.h" TMP REGEX "^#define JPEGXL_MINOR_VERSION .*$") 26 | string(REGEX MATCHALL "[0-9]+" JPEGXL_MINOR_VERSION ${TMP}) 27 | file(STRINGS "${JXL_INCLUDE_DIR}/jxl/version.h" TMP REGEX "^#define JPEGXL_PATCH_VERSION .*$") 28 | string(REGEX MATCHALL "[0-9]+" JPEGXL_PATCH_VERSION ${TMP}) 29 | set(JXL_VERSION "${JPEGXL_MAJOR_VERSION}.${JPEGXL_MINOR_VERSION}.${JPEGXL_PATCH_VERSION}") 30 | endif() 31 | 32 | find_library(JXL_LIBRARY NAMES jxl) 33 | mark_as_advanced(JXL_LIBRARY JXL_VERSION) 34 | 35 | find_library(JXL_THREADS_LIBRARY NAMES jxl_threads) 36 | mark_as_advanced(JXL_THREADS_LIBRARY) 37 | 38 | find_library(JXL_CMS_LIBRARY NAMES jxl_cms) 39 | mark_as_advanced(JXL_CMS_LIBRARY) 40 | 41 | find_package_handle_standard_args( 42 | JXL 43 | REQUIRED_VARS JXL_LIBRARY JXL_THREADS_LIBRARY JXL_INCLUDE_DIR 44 | VERSION_VAR JXL_VERSION 45 | ) 46 | 47 | if(JXL_FOUND) 48 | set(JXL_LIBRARIES ${JXL_LIBRARY} ${JXL_THREADS_LIBRARY} ${JXL_CMS_LIBRARY}) 49 | set(JXL_INCLUDES ${JXL_INCLUDE_DIR}) 50 | 51 | if(JXL_LIBRARY AND NOT TARGET jxl) 52 | add_library(jxl SHARED IMPORTED) 53 | set_target_properties( 54 | jxl 55 | PROPERTIES INTERFACE_COMPILE_FEATURES "cxx_std_11" 56 | INTERFACE_INCLUDE_DIRECTORIES "${JXL_INCLUDES}" 57 | INTERFACE_LINK_LIBRARIES "${JXL_LIBRARY}" 58 | IMPORTED_LOCATION "${JXL_LIBRARY}" 59 | ) 60 | endif() 61 | 62 | if(JXL_THREADS_LIBRARY AND NOT TARGET jxl_threads) 63 | add_library(jxl_threads SHARED IMPORTED) 64 | set_target_properties( 65 | jxl_threads 66 | PROPERTIES INTERFACE_COMPILE_FEATURES "cxx_std_11" 67 | INTERFACE_INCLUDE_DIRECTORIES "${JXL_INCLUDES}" 68 | INTERFACE_LINK_LIBRARIES "${JXL_THREADS_LIBRARY}" 69 | IMPORTED_LOCATION "${JXL_THREADS_LIBRARY}" 70 | ) 71 | endif() 72 | 73 | if(JXL_CMS_LIBRARY AND NOT TARGET jxl_cms) 74 | add_library(jxl_cms SHARED IMPORTED) 75 | set_target_properties( 76 | jxl_cms 77 | PROPERTIES INTERFACE_COMPILE_FEATURES "cxx_std_11" 78 | INTERFACE_INCLUDE_DIRECTORIES "${JXL_INCLUDES}" 79 | INTERFACE_LINK_LIBRARIES "${JXL_CMS_LIBRARY}" 80 | IMPORTED_LOCATION "${JXL_CMS_LIBRARY}" 81 | ) 82 | endif() 83 | endif(JXL_FOUND) 84 | -------------------------------------------------------------------------------- /cmake/modules/FindLCMS2.cmake: -------------------------------------------------------------------------------- 1 | # cmake-format: off 2 | # 3 | # - Find LCMS2 4 | # Find the LCMS2 includes and library 5 | # 6 | # This module defines 7 | # LCMS2_INCLUDE_DIRS, where to find lcms2.h 8 | # LCMS2_LIBRARIES, the libraries needed to use LCMS2. 9 | # LCMS2_VERSION, The value of LCMS_VERSION defined in lcms2.h 10 | # LCMS2_FOUND, If false, do not try to use LCMS2. 11 | # 12 | # If found, it will also define an imported targets for the library: 13 | # - lcms2 14 | # 15 | 16 | # Copyright (c) the JPEG XL Project Authors. All rights reserved. 17 | # 18 | # Use of this source code is governed by a BSD-style 19 | # license that can be found in the LICENSE file. 20 | # 21 | # cmake-format: on 22 | 23 | find_package(PkgConfig QUIET) 24 | if(PkgConfig_FOUND) 25 | pkg_check_modules(PC_LCMS2 QUIET libLCMS2) 26 | set(LCMS2_VERSION ${PC_LCMS2_VERSION}) 27 | endif() 28 | 29 | find_path( 30 | LCMS2_INCLUDE_DIR 31 | NAMES lcms2.h 32 | HINTS ${PC_LCMS2_INCLUDEDIR} ${PC_LCMS2_INCLUDE_DIRS} 33 | ) 34 | 35 | find_library( 36 | LCMS2_LIBRARY 37 | NAMES ${LCMS2_NAMES} lcms2 liblcms2 lcms-2 liblcms-2 38 | HINTS ${PC_LCMS2_LIBDIR} ${PC_LCMS2_LIBRARY_DIRS} 39 | ) 40 | 41 | if(LCMS2_INCLUDE_DIR AND NOT LCMS_VERSION) 42 | file(READ ${LCMS2_INCLUDE_DIR}/lcms2.h LCMS2_VERSION_CONTENT) 43 | string(REGEX MATCH "#define[ \t]+LCMS_VERSION[ \t]+([0-9]+)[ \t]*\n" LCMS2_VERSION_MATCH ${LCMS2_VERSION_CONTENT}) 44 | if(LCMS2_VERSION_MATCH) 45 | string(SUBSTRING ${CMAKE_MATCH_1} 0 1 LCMS2_VERSION_MAJOR) 46 | string(SUBSTRING ${CMAKE_MATCH_1} 1 2 LCMS2_VERSION_MINOR) 47 | set(LCMS2_VERSION "${LCMS2_VERSION_MAJOR}.${LCMS2_VERSION_MINOR}") 48 | endif() 49 | endif() 50 | 51 | include(FindPackageHandleStandardArgs) 52 | find_package_handle_standard_args( 53 | LCMS2 54 | FOUND_VAR LCMS2_FOUND 55 | REQUIRED_VARS LCMS2_LIBRARY LCMS2_INCLUDE_DIR 56 | VERSION_VAR LCMS2_VERSION 57 | ) 58 | 59 | if(LCMS2_LIBRARY AND NOT TARGET lcms2) 60 | add_library(lcms2 INTERFACE IMPORTED GLOBAL) 61 | 62 | if(CMAKE_VERSION VERSION_LESS "3.13.5") 63 | set_property(TARGET lcms2 PROPERTY INTERFACE_INCLUDE_DIRECTORIES ${LCMS2_INCLUDE_DIR}) 64 | target_link_libraries(lcms2 INTERFACE ${LCMS2_LIBRARY}) 65 | set_property(TARGET lcms2 PROPERTY INTERFACE_COMPILE_OPTIONS ${PC_LCMS2_CFLAGS_OTHER}) 66 | else() 67 | target_include_directories(lcms2 INTERFACE ${LCMS2_INCLUDE_DIR}) 68 | target_link_libraries(lcms2 INTERFACE ${LCMS2_LIBRARY}) 69 | target_link_options(lcms2 INTERFACE ${PC_LCMS2_LDFLAGS_OTHER}) 70 | target_compile_options(lcms2 INTERFACE ${PC_LCMS2_CFLAGS_OTHER}) 71 | endif() 72 | endif() 73 | 74 | mark_as_advanced(LCMS2_INCLUDE_DIR LCMS2_LIBRARY) 75 | 76 | if(LCMS2_FOUND) 77 | set(LCMS2_LIBRARIES ${LCMS2_LIBRARY}) 78 | set(LCMS2_INCLUDE_DIRS ${LCMS2_INCLUDE_DIR}) 79 | endif() 80 | -------------------------------------------------------------------------------- /cmake/modules/FindLibheif.cmake: -------------------------------------------------------------------------------- 1 | # cmake-format: off 2 | # 3 | # Module to find LIBHEIF 4 | # 5 | # This module will first look in the directories defined by the variables: 6 | # - Libheif_ROOT, LIBHEIF_INCLUDE_PATH, LIBHEIF_LIBRARY_PATH 7 | # 8 | # It then defines the following variables: 9 | # 10 | # - Libheif_FOUND True if LIBHEIF was found. 11 | # - LIBHEIF_INCLUDES Where to find LIBHEIF headers 12 | # - LIBHEIF_LIBRARIES List of libraries to link against when using LIBHEIF 13 | # - LIBHEIF_VERSION Version of LIBHEIF (e.g., 3.6.2) 14 | # 15 | # If found, it will also define an imported targets for the library: 16 | # - heif 17 | # 18 | # cmake-format: on 19 | 20 | include(FindPackageHandleStandardArgs) 21 | 22 | find_path( 23 | LIBHEIF_INCLUDE_DIR libheif/heif_version.h 24 | HINTS ${LIBHEIF_INCLUDE_PATH} ENV LIBHEIF_INCLUDE_PATH 25 | DOC "The directory where libheif headers reside" 26 | ) 27 | 28 | find_library( 29 | LIBHEIF_LIBRARY heif 30 | HINTS ${LIBHEIF_LIBRARY_PATH} ENV LIBHEIF_LIBRARY_PATH 31 | DOC "The directory where libheif libraries reside" 32 | ) 33 | 34 | if(LIBHEIF_INCLUDE_DIR) 35 | file(STRINGS "${LIBHEIF_INCLUDE_DIR}/libheif/heif_version.h" TMP REGEX "^#define LIBHEIF_VERSION[ \t].*$") 36 | string(REGEX MATCHALL "[0-9.]+" LIBHEIF_VERSION ${TMP}) 37 | endif() 38 | 39 | find_package_handle_standard_args(Libheif REQUIRED_VARS LIBHEIF_INCLUDE_DIR LIBHEIF_LIBRARY) 40 | 41 | if(Libheif_FOUND) 42 | set(LIBHEIF_INCLUDES "${LIBHEIF_INCLUDE_DIR}") 43 | set(LIBHEIF_LIBRARIES "${LIBHEIF_LIBRARY}") 44 | 45 | if(NOT TARGET heif) 46 | add_library(heif UNKNOWN IMPORTED) 47 | set_target_properties( 48 | heif PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${LIBHEIF_INCLUDES}" IMPORTED_LOCATION "${LIBHEIF_LIBRARIES}" 49 | ) 50 | endif() 51 | endif() 52 | 53 | mark_as_advanced(LIBHEIF_INCLUDES LIBHEIF_LIBRARIES LIBHEIF_VERSION) 54 | -------------------------------------------------------------------------------- /cmake/modules/Findlibuhdr.cmake: -------------------------------------------------------------------------------- 1 | # cmake-format: off 2 | # Module to find libuhdr 3 | # 4 | # This module defines the following variables: 5 | # 6 | # - libuhdr_FOUND True if libuhdr was found. 7 | # - LIBUHDR_INCLUDES Directory to include for libuhdr headers 8 | # - LIBUHDR_LIBRARY Library to link to 9 | # 10 | # If found, it will also define an imported targets for the library: 11 | # - uhdr 12 | # 13 | # cmake-format: on 14 | 15 | include(FindPackageHandleStandardArgs) 16 | 17 | find_path( 18 | LIBUHDR_INCLUDES 19 | NAMES ultrahdr_api.h 20 | PATH_SUFFIXES include 21 | ) 22 | mark_as_advanced(LIBUHDR_INCLUDES) 23 | 24 | find_library(LIBUHDR_LIBRARY uhdr PATH_SUFFIXES lib) 25 | 26 | find_package_handle_standard_args(libuhdr REQUIRED_VARS LIBUHDR_INCLUDES LIBUHDR_LIBRARY) 27 | 28 | if(libuhdr_FOUND) 29 | if(NOT TARGET core) 30 | add_library(core UNKNOWN IMPORTED) 31 | set_target_properties( 32 | core 33 | PROPERTIES CXX_STANDARD 17 34 | INTERFACE_INCLUDE_DIRECTORIES "${LIBUHDR_INCLUDES}" 35 | INTERFACE_LINK_LIBRARIES "${LIBUHDR_LIBRARY}" 36 | IMPORTED_LOCATION "${LIBUHDR_LIBRARY}" 37 | ) 38 | endif() 39 | endif(libuhdr_FOUND) 40 | -------------------------------------------------------------------------------- /cmake/osx-post-install.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.1) 2 | 3 | # Add a symlink to /usr/local/bin so we can launch HDRView from the commandline 4 | execute_process(COMMAND rm -f /usr/local/bin/hdrview) 5 | execute_process(COMMAND ln -s /Applications/HDRView.app/Contents/MacOS/HDRView /usr/local/bin/hdrview) 6 | execute_process(COMMAND ln -s /Applications/HDRView.app/Contents/Resources/bin/hdrbatch /usr/local/bin/hdrbatch) 7 | -------------------------------------------------------------------------------- /cmake/sanitizers.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Copyright (C) 2018 by George Cave - gcave@stablecoder.ca 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with 5 | # the License. You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an 10 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the 11 | # specific language governing permissions and limitations under the License. 12 | 13 | set(USE_SANITIZER 14 | "" 15 | CACHE 16 | STRING 17 | "Compile with a sanitizer. Options are: Address, Memory, MemoryWithOrigins, Undefined, Thread, Leak, 'Address;Undefined'" 18 | ) 19 | 20 | message(STATUS "Using sanitizer: ${USE_SANITIZER}") 21 | 22 | function(append value) 23 | foreach(variable ${ARGN}) 24 | set(${variable} "${${variable}} ${value}" PARENT_SCOPE) 25 | endforeach(variable) 26 | endfunction() 27 | 28 | if(USE_SANITIZER) 29 | append("-fno-omit-frame-pointer" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) 30 | 31 | if(UNIX) 32 | 33 | if(uppercase_CMAKE_BUILD_TYPE STREQUAL "DEBUG") 34 | append("-O1" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) 35 | endif() 36 | 37 | if(USE_SANITIZER MATCHES "([Aa]ddress);([Uu]ndefined)" OR USE_SANITIZER MATCHES "([Uu]ndefined);([Aa]ddress)") 38 | message(STATUS "Building with Address, Undefined sanitizers") 39 | append("-fsanitize=address,undefined" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) 40 | elseif(USE_SANITIZER MATCHES "([Aa]ddress)") 41 | # Optional: -fno-optimize-sibling-calls -fsanitize-address-use-after-scope 42 | message(STATUS "Building with Address sanitizer") 43 | append("-fsanitize=address" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) 44 | elseif(USE_SANITIZER MATCHES "([Mm]emory([Ww]ith[Oo]rigins)?)") 45 | # Optional: -fno-optimize-sibling-calls -fsanitize-memory-track-origins=2 46 | append("-fsanitize=memory" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) 47 | if(USE_SANITIZER MATCHES "([Mm]emory[Ww]ith[Oo]rigins)") 48 | message(STATUS "Building with MemoryWithOrigins sanitizer") 49 | append("-fsanitize-memory-track-origins" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) 50 | else() 51 | message(STATUS "Building with Memory sanitizer") 52 | endif() 53 | elseif(USE_SANITIZER MATCHES "([Uu]ndefined)") 54 | message(STATUS "Building with Undefined sanitizer") 55 | append("-fsanitize=undefined" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) 56 | if(EXISTS "${BLACKLIST_FILE}") 57 | append("-fsanitize-blacklist=${BLACKLIST_FILE}" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) 58 | endif() 59 | elseif(USE_SANITIZER MATCHES "([Tt]hread)") 60 | message(STATUS "Building with Thread sanitizer") 61 | append("-fsanitize=thread" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) 62 | elseif(USE_SANITIZER MATCHES "([Ll]eak)") 63 | message(STATUS "Building with Leak sanitizer") 64 | append("-fsanitize=leak" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) 65 | else() 66 | message(FATAL_ERROR "Unsupported value of USE_SANITIZER: ${USE_SANITIZER}") 67 | endif() 68 | elseif(MSVC) 69 | if(USE_SANITIZER MATCHES "([Aa]ddress)") 70 | message(STATUS "Building with Address sanitizer") 71 | append("-fsanitize=address" CMAKE_C_FLAGS CMAKE_CXX_FLAGS) 72 | else() 73 | message(FATAL_ERROR "This sanitizer not yet supported in the MSVC environment: ${USE_SANITIZER}") 74 | endif() 75 | else() 76 | message(FATAL_ERROR "USE_SANITIZER is not supported on this platform.") 77 | endif() 78 | 79 | endif() 80 | -------------------------------------------------------------------------------- /custom_http_server.py: -------------------------------------------------------------------------------- 1 | import http.server 2 | import socketserver 3 | 4 | PORT = 8001 5 | 6 | 7 | class CustomHTTPRequestHandler(http.server.SimpleHTTPRequestHandler): 8 | def end_headers(self): 9 | # Add custom headers here 10 | # self.send_header("Cross-Origin-Opener-Policy", "same-origin") 11 | # self.send_header("Cross-Origin-Embedder-Policy", "require-corp") 12 | # Call the superclass's end_headers method 13 | super().end_headers() 14 | 15 | 16 | # Create the server using socketserver.TCPServer 17 | with socketserver.TCPServer(("", PORT), CustomHTTPRequestHandler) as httpd: 18 | print(f"Serving on port {PORT}") 19 | httpd.serve_forever() 20 | -------------------------------------------------------------------------------- /resources/gamma-grid.exr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkjarosz/hdrview/5ef1c6258de36bead0aee8919e6748eb9fbe7829/resources/gamma-grid.exr -------------------------------------------------------------------------------- /resources/gamma-grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkjarosz/hdrview/5ef1c6258de36bead0aee8919e6748eb9fbe7829/resources/gamma-grid.png -------------------------------------------------------------------------------- /resources/icon.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkjarosz/hdrview/5ef1c6258de36bead0aee8919e6748eb9fbe7829/resources/icon.pdf -------------------------------------------------------------------------------- /resources/icon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkjarosz/hdrview/5ef1c6258de36bead0aee8919e6748eb9fbe7829/resources/icon.psd -------------------------------------------------------------------------------- /resources/sample-colormap.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import argparse 5 | import matplotlib.pyplot as plt 6 | import seaborn as sns 7 | import numpy as np 8 | 9 | def srgb_to_linear(srgb): 10 | linear = np.where(srgb <= 0.04045, srgb / 12.92, ((srgb + 0.055) / 1.055) ** 2.4) 11 | return linear 12 | 13 | def linearize(values, do_linearize): 14 | return srgb_to_linear(values) if do_linearize else values 15 | 16 | def get_colormap(name): 17 | try: 18 | return sns.color_palette(name, as_cmap=True) 19 | except ValueError: 20 | raise ValueError(f"Colormap '{name}' not found in Matplotlib or Seaborn.") 21 | 22 | def main(arguments): 23 | parser = argparse.ArgumentParser(description="Sample a colormap at regularly spaced intervals.") 24 | parser.add_argument("name", type=str, help="The name of the matplotlib or seaborn colormap to sample.") 25 | parser.add_argument("-n", "--num-samples", type=int, default=256, help="The number of samples to generate.") 26 | parser.add_argument("-a", "--include-alpha", action="store_true", help="Include the alpha channel in the output.") 27 | parser.add_argument("-l", "--linearize", action="store_true", help="Output linear (instead of sRGB encoded) values.") 28 | parser.add_argument("-b", "--bit-depth", type=int, choices=[8, 16, 32], default=32, help="Bit depth of the output values.") 29 | 30 | args = parser.parse_args(arguments) 31 | 32 | xs = np.linspace(0, 1, args.num_samples) 33 | print(xs) 34 | 35 | cmap = get_colormap(args.name) 36 | rgba_values = cmap(xs) 37 | 38 | rgb = rgba_values[:, :3] 39 | alpha = rgba_values[:, 3] if rgba_values.shape[1] == 4 else np.ones_like(xs) 40 | values = linearize(rgb, args.linearize) 41 | if args.include_alpha: 42 | values = np.column_stack((values, alpha)) 43 | 44 | if args.bit_depth == 8: 45 | values = (values * 255).astype(int) 46 | values = ",\n ".join(["IM_COL32(" + ", ".join([str(y) for y in x]) + ")" for x in values]) + "," 47 | print(f"static const std::vector data = {{\n {values}\n}};") 48 | elif args.bit_depth == 16: 49 | values = (values * 65535).astype(int) 50 | values = ",\n ".join(["{" + ", ".join([str(y) for y in x]) + "}" for x in values]) + "," 51 | print(f"static const std::vector data = {{\n {values}\n}};") 52 | else: 53 | values = ",\n ".join(["{" + ", ".join([f"{y:.6f}f" for y in x]) + "}" for x in values]) + "," 54 | print(f"static const std::vector data = {{\n {values}\n}};") 55 | 56 | if __name__ == "__main__": 57 | sys.exit(main(sys.argv[1:])) -------------------------------------------------------------------------------- /resources/screenshot-command-palette.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkjarosz/hdrview/5ef1c6258de36bead0aee8919e6748eb9fbe7829/resources/screenshot-command-palette.png -------------------------------------------------------------------------------- /resources/screenshot-dithered.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkjarosz/hdrview/5ef1c6258de36bead0aee8919e6748eb9fbe7829/resources/screenshot-dithered.png -------------------------------------------------------------------------------- /resources/screenshot-ipad.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkjarosz/hdrview/5ef1c6258de36bead0aee8919e6748eb9fbe7829/resources/screenshot-ipad.jpg -------------------------------------------------------------------------------- /resources/screenshot-mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkjarosz/hdrview/5ef1c6258de36bead0aee8919e6748eb9fbe7829/resources/screenshot-mac.png -------------------------------------------------------------------------------- /resources/screenshot-no-dither.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkjarosz/hdrview/5ef1c6258de36bead0aee8919e6748eb9fbe7829/resources/screenshot-no-dither.png -------------------------------------------------------------------------------- /resources/screenshot-zoomed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wkjarosz/hdrview/5ef1c6258de36bead0aee8919e6748eb9fbe7829/resources/screenshot-zoomed.png -------------------------------------------------------------------------------- /src/Imath_to_linalg.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Wojciech Jarosz. All rights reserved. 3 | // Use of this source code is governed by a BSD-style license that can 4 | // be found in the LICENSE.txt file. 5 | // 6 | 7 | #pragma once 8 | 9 | #include "linalg.h" 10 | #include 11 | #include 12 | 13 | // linalg and Imath use the same memory layout, so we can convert via linalg's pointer constructors 14 | // Conversion function for Imath::Vec2 to linalg::vec 15 | template 16 | linalg::vec to_linalg(const Imath::Vec2 &v) 17 | { 18 | return linalg::vec(&v.x); 19 | } 20 | 21 | // Conversion function for Imath::Vec3 to linalg::vec 22 | template 23 | linalg::vec to_linalg(const Imath::Vec3 &v) 24 | { 25 | return linalg::vec(&v.x); 26 | } 27 | 28 | // Conversion function for Imath::Vec4 to linalg::vec 29 | template 30 | linalg::vec to_linalg(const Imath::Vec4 &v) 31 | { 32 | return linalg::vec(&v.x); 33 | } 34 | 35 | // Conversion function for Imath::Matrix22 to linalg::mat 36 | template 37 | linalg::mat to_linalg(const Imath::Matrix22 &m) 38 | { 39 | return linalg::mat(&m.x[0][0]); 40 | } 41 | 42 | // Conversion function for Imath::Matrix33 to linalg::mat 43 | template 44 | linalg::mat to_linalg(const Imath::Matrix33 &m) 45 | { 46 | return linalg::mat(&m.x[0][0]); 47 | } 48 | 49 | // Conversion function for Imath::Matrix44 to linalg::mat 50 | template 51 | linalg::mat to_linalg(const Imath::Matrix44 &m) 52 | { 53 | return linalg::mat(&m.x[0][0]); 54 | } 55 | -------------------------------------------------------------------------------- /src/array2d.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Wojciech Jarosz. All rights reserved. 3 | // Use of this source code is governed by a BSD-style license that can 4 | // be found in the LICENSE.txt file. 5 | // 6 | 7 | #pragma once 8 | 9 | #include "fwd.h" 10 | #include 11 | 12 | //! Generic, resizable, 2D array class. 13 | template 14 | class Array2D 15 | { 16 | public: 17 | //@{ \name Constructors and destructors 18 | Array2D() : m_data(), m_size(0) {} // empty array, 0 by 0 elements 19 | Array2D(int2 size, const T &value = T(0.)) : 20 | m_data(size.x * size.y, value), m_size(size) {} // size.x by size.y elements 21 | Array2D(int size_x, int size_y, const T &value = T(0.)) : 22 | Array2D(int2{size_x, size_y}, value) {} // size.x by size.y elements 23 | 24 | Array2D(const Array2D &o) : m_data(o.m_data), m_size(o.m_size) {} 25 | Array2D &operator=(const Array2D &o) 26 | { 27 | m_data = o.m_data; 28 | m_size = o.m_size; 29 | return *this; 30 | }; 31 | 32 | // // prevent copying by deleting the copy constructor and copy assignment operator 33 | // Array2D(const Array2D &) = delete; 34 | // Array2D &operator=(const Array2D &) = delete; 35 | 36 | // Move constructor 37 | Array2D(Array2D &&other) noexcept : m_data(std::move(other.m_data)), m_size(other.m_size) 38 | { 39 | // Reset the source object 40 | other.m_size = int2{0}; 41 | } 42 | 43 | // Move assignment operator 44 | Array2D &operator=(Array2D &&other) noexcept 45 | { 46 | if (this != &other) 47 | { 48 | // Use move semantics for the vector 49 | m_data = std::move(other.m_data); 50 | 51 | // Copy other members 52 | m_size = other.m_size; 53 | 54 | // Reset the source object 55 | other.m_size = int2{0}; 56 | } 57 | return *this; 58 | } 59 | 60 | //@} 61 | 62 | //@{ \name Element access 63 | T &operator()(int x, int y) { return m_data[y * m_size.x + x]; } 64 | const T &operator()(int x, int y) const { return m_data[y * m_size.x + x]; } 65 | T &operator()(int2 p) { return operator()(p.x, p.y); } 66 | const T &operator()(int2 p) const { return operator()(p.x, p.y); } 67 | T &operator()(int i) { return m_data[i]; } 68 | const T &operator()(int i) const { return m_data[i]; } 69 | const T *data() const { return &(m_data[0]); } 70 | T *data() { return &(m_data[0]); } 71 | //@} 72 | 73 | //@{ \name Dimension sizes 74 | int num_elements() const { return m_size.x * m_size.y; } 75 | int2 size() const { return m_size; } 76 | int width() const { return m_size.x; } 77 | int height() const { return m_size.y; } 78 | //@} 79 | 80 | void resize(int2 size, const T &value = T(0.)); 81 | void reset(const T &value = T(0.)); 82 | void operator=(const T &); 83 | 84 | protected: 85 | std::vector m_data; 86 | int2 m_size; 87 | }; 88 | 89 | template 90 | inline void Array2D::resize(int2 size, const T &value) 91 | { 92 | if (size == m_size) 93 | return; 94 | 95 | m_data.resize(size.x * size.y, value); 96 | m_size = size; 97 | } 98 | 99 | template 100 | inline void Array2D::reset(const T &value) 101 | { 102 | for (int i = 0; i < num_elements(); ++i) m_data[i] = value; 103 | } 104 | 105 | template 106 | inline void Array2D::operator=(const T &value) 107 | { 108 | reset(value); 109 | } 110 | 111 | using Array2Di = Array2D; 112 | using Array2Dd = Array2D; 113 | using Array2Df = Array2D; 114 | -------------------------------------------------------------------------------- /src/async.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Wojciech Jarosz. All rights reserved. 3 | // Use of this source code is governed by a BSD-style license that can 4 | // be found in the LICENSE.txt file. 5 | // 6 | #pragma once 7 | 8 | #include "progress.h" 9 | #include "scheduler.h" 10 | #include 11 | #include 12 | 13 | template 14 | class AsyncTask 15 | { 16 | public: 17 | using TaskFunc = std::function &canceled)>; 18 | using NoProgressTaskFunc = std::function; 19 | 20 | /*! 21 | * Create an asynchronous task that can report back on its progress 22 | * @param compute The function to execute asynchronously 23 | */ 24 | AsyncTask(TaskFunc f, Scheduler *s = nullptr) : 25 | m_state(std::make_shared([f](std::atomic &canceled) { return f(canceled); }, s)) 26 | { 27 | } 28 | 29 | /*! 30 | * Create an asynchronous task without progress updates 31 | * @param compute The function to execute asynchronously 32 | */ 33 | AsyncTask(NoProgressTaskFunc f, Scheduler *s = nullptr) : 34 | m_state(std::make_shared([f](std::atomic &) { return f(); }, s)) 35 | { 36 | } 37 | 38 | ~AsyncTask() 39 | { 40 | cancel(); 41 | // no need to wait, the task continues running and the scheduler will clean it up 42 | } 43 | 44 | /*! 45 | * Start the computation (if it hasn't already been started) 46 | */ 47 | void compute() 48 | { 49 | if (m_state->started) 50 | return; 51 | 52 | struct Payload 53 | { 54 | std::shared_ptr state; 55 | }; 56 | 57 | auto callback = [](int, int, void *payload) 58 | { 59 | auto &state = ((Payload *)payload)->state; 60 | state->value = state->f(state->canceled); 61 | }; 62 | auto deleter = [](int, int, void *payload) { delete (Payload *)payload; }; 63 | 64 | Payload *payload = new Payload{m_state}; 65 | 66 | auto pool = m_state->threadpool ? m_state->threadpool : Scheduler::singleton(); 67 | m_state->task = pool->parallelize_async(1, payload, callback, deleter); 68 | m_state->started = true; 69 | } 70 | 71 | /*! 72 | * Waits until the task has finished, and returns the result. 73 | * The tasks return value is cached, so get can be called multiple times. 74 | * 75 | * @return The result of the computation 76 | */ 77 | T &get() 78 | { 79 | m_state->task.wait(); 80 | return m_state->value; 81 | } 82 | 83 | /*! 84 | * Query the progress of the task. 85 | * 86 | * @return The percentage done, ranging from 0.f to 100.f, 87 | * or -1 to indicate busy if the task doesn't report back progress 88 | */ 89 | // float progress() const { return m_progress.progress(); } 90 | 91 | // void set_progress(float p) { m_progress.reset_progress(p); } 92 | 93 | /// Query whether the task is canceled. 94 | bool canceled() const { return m_state->canceled; } 95 | 96 | void cancel() 97 | { 98 | spdlog::trace("Canceling async computation"); 99 | m_state->canceled = true; 100 | } 101 | 102 | /*! 103 | * @return true if the computation has finished 104 | */ 105 | bool ready() const { return m_state->started && m_state->task.ready(); } 106 | 107 | private: 108 | struct SharedState 109 | { 110 | TaskFunc f; 111 | Scheduler *threadpool{nullptr}; 112 | std::atomic canceled{false}; 113 | bool started{false}; 114 | Scheduler::TaskTracker task; 115 | T value; 116 | 117 | SharedState(TaskFunc fn, Scheduler *s) : f(fn), threadpool(s), canceled(false), started(false), task(), value() 118 | { 119 | } 120 | }; 121 | std::shared_ptr m_state; //!< Shared state between this AsyncTask and the Scheduler executing the task 122 | }; -------------------------------------------------------------------------------- /src/box.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Wojciech Jarosz. All rights reserved. 3 | // Use of this source code is governed by a BSD-style license that can 4 | // be found in the LICENSE.txt file. 5 | // 6 | 7 | #pragma once 8 | 9 | #include "fwd.h" 10 | #include // for min, max 11 | #include 12 | #include // for numeric_limits 13 | 14 | //! Represents a bounded interval in higher dimensions. 15 | /*! 16 | Box is an N-D interval. 17 | */ 18 | template 19 | class Box 20 | { 21 | public: 22 | static constexpr size_t Dims = Dims_; 23 | using Value = Value_; 24 | using Vec = Vec_; 25 | using BoxT = Box; 26 | 27 | Vec min; //!< The lower-bound of the interval 28 | Vec max; //!< The upper-bound of the interval 29 | 30 | //----------------------------------------------------------------------- 31 | //@{ \name Constructors 32 | //----------------------------------------------------------------------- 33 | Box() { make_empty(); } 34 | Box(const Vec &point) : min(point), max(point) {} 35 | Box(const Vec &minT, const Vec &maxT) : min(minT), max(maxT) {} 36 | 37 | template 38 | Box(const Box &box) : min((Vec)box.min), max((Vec)box.max) 39 | { 40 | } 41 | 42 | template 43 | Box(const Box &box1, const Box &box2) : min((Vec)box1.min), max((Vec)box1.max) 44 | { 45 | enclose((BoxT)box2); 46 | } 47 | //@} 48 | 49 | //----------------------------------------------------------------------- 50 | //@{ \name Equality and inequality 51 | //----------------------------------------------------------------------- 52 | bool operator==(const BoxT &other) const { return (min == other.min && max == other.max); } 53 | bool operator!=(const BoxT &other) const { return (min != other.min || max != other.max); } 54 | //@} 55 | 56 | //----------------------------------------------------------------------- 57 | //@{ \name Box manipulation 58 | //----------------------------------------------------------------------- 59 | BoxT &make_empty() 60 | { 61 | min = Vec(std::numeric_limits::max()); 62 | max = Vec(std::numeric_limits::lowest()); 63 | return *this; 64 | } 65 | BoxT expanded(Value d) const { return BoxT(min - d, max + d); } 66 | BoxT expanded(const Vec &d) const { return BoxT(min - d, max + d); } 67 | BoxT expanded(const BoxT &d) const { return BoxT(min - d.min, max + d.max); } 68 | BoxT &set_size(const Vec &s) 69 | { 70 | max = min + s; 71 | return *this; 72 | } 73 | BoxT &enclose(const Vec &point) 74 | { 75 | for (size_t i = 0; i < Dims; ++i) 76 | { 77 | min[i] = std::min(point[i], min[i]); 78 | max[i] = std::max(point[i], max[i]); 79 | } 80 | return *this; 81 | } 82 | BoxT &enclose(const BoxT &box) 83 | { 84 | for (size_t i = 0; i < Dims; ++i) 85 | { 86 | min[i] = std::min(box.min[i], min[i]); 87 | max[i] = std::max(box.max[i], max[i]); 88 | } 89 | return *this; 90 | } 91 | BoxT &intersect(const BoxT &box) 92 | { 93 | for (size_t i = 0; i < Dims; ++i) 94 | { 95 | min[i] = std::max(box.min[i], min[i]); 96 | max[i] = std::min(box.max[i], max[i]); 97 | } 98 | return *this; 99 | } 100 | BoxT &move_min_to(const Vec &newMin) 101 | { 102 | Vec diff(newMin - min); 103 | min = newMin; 104 | max += diff; 105 | return *this; 106 | } 107 | BoxT &move_max_to(const Vec &newMax) 108 | { 109 | Vec diff(newMax - max); 110 | max = newMax; 111 | min += diff; 112 | return *this; 113 | } 114 | BoxT &make_valid() 115 | { 116 | for (size_t i = 0; i < Dims; ++i) 117 | { 118 | if (min[i] > max[i]) 119 | std::swap(min[i], max[i]); 120 | } 121 | return *this; 122 | } 123 | //@} 124 | 125 | //----------------------------------------------------------------------- 126 | //@{ \name Query functions 127 | //----------------------------------------------------------------------- 128 | Vec size() const { return max - min; } 129 | Vec center() const { return (max + min) / Value(2); } 130 | Vec clamp(Vec point) const 131 | { 132 | for (size_t i = 0; i < Dims; ++i) point[i] = std::min(std::max(point[i], min[i]), max[i]); 133 | return point; 134 | } 135 | template 136 | bool contains(const Vec &point) const 137 | { 138 | if constexpr (Inclusive) 139 | { 140 | for (size_t i = 0; i < Dims; ++i) 141 | if (point[i] <= min[i] || point[i] >= max[i]) 142 | return false; 143 | return true; 144 | } 145 | else 146 | { 147 | for (size_t i = 0; i < Dims; ++i) 148 | if (point[i] < min[i] || point[i] > max[i]) 149 | return false; 150 | return true; 151 | } 152 | } 153 | template 154 | bool intersects(const BoxT &box) const 155 | { 156 | if constexpr (Inclusive) 157 | { 158 | for (size_t i = 0; i < Dims; ++i) 159 | if (box.max[i] <= min[i] || box.min[i] >= max[i]) 160 | return false; 161 | return true; 162 | } 163 | else 164 | { 165 | for (size_t i = 0; i < Dims; ++i) 166 | if (box.max[i] < min[i] || box.min[i] > max[i]) 167 | return false; 168 | return true; 169 | } 170 | } 171 | Value volume() const 172 | { 173 | Value ret_val(1); 174 | Vec s = size(); 175 | for (size_t i = 0; i < Dims; ++i) ret_val *= s[i]; 176 | return ret_val; 177 | } 178 | Value area() const 179 | { 180 | Value ret_val(0); 181 | Vec s = size(); 182 | for (size_t i = 0; i < Dims; ++i) 183 | for (size_t j = i + 1; j < Dims; j++) ret_val += s[i] * s[j]; 184 | return 2 * ret_val; 185 | } 186 | size_t major_axis() const 187 | { 188 | size_t major = 0; 189 | Vec s = size(); 190 | 191 | for (size_t i = 1; i < Dims; ++i) 192 | if (s[i] > s[major]) 193 | major = i; 194 | return major; 195 | } 196 | void bounding_sphere(Vec *center, Value *radius) const 197 | { 198 | *center = center(); 199 | *radius = intersects(*center) ? (*center - max).length() : 0.0f; 200 | } 201 | //@} 202 | 203 | //----------------------------------------------------------------------- 204 | //@{ \name Classification 205 | //----------------------------------------------------------------------- 206 | bool has_volume() const 207 | { 208 | for (size_t i = 0; i < Dims; ++i) 209 | if (max[i] <= min[i]) 210 | return false; 211 | return true; 212 | } 213 | bool is_empty() const 214 | { 215 | for (size_t i = 0; i < Dims; ++i) 216 | if (max[i] < min[i]) 217 | return true; 218 | return false; 219 | } 220 | //@} 221 | }; 222 | 223 | template 224 | inline std::ostream &operator<<(std::ostream &o, const Box &b) 225 | { 226 | return o << "[(" << b.min << "),(" << b.max << ")]"; 227 | } 228 | -------------------------------------------------------------------------------- /src/cliformatter.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Wojciech Jarosz. All rights reserved. 3 | // Use of this source code is governed by a BSD-style license that can 4 | // be found in the LICENSE.txt file. 5 | // 6 | 7 | #pragma once 8 | #include 9 | #include 10 | #include 11 | 12 | class ColorFormatter : public CLI::Formatter 13 | { 14 | public: 15 | std::string make_option_name(const CLI::Option *opt, bool is_positional) const override 16 | { 17 | return fmt::format(fmt::emphasis::bold | fg(fmt::color::cornflower_blue), "{}", 18 | CLI::Formatter::make_option_name(opt, is_positional)); 19 | } 20 | std::string make_option_opts(const CLI::Option *opt) const override 21 | { 22 | return fmt::format(fg(fmt::color::light_sea_green), "{}", CLI::Formatter::make_option_opts(opt)); 23 | } 24 | std::string make_option_desc(const CLI::Option *opt) const override 25 | { 26 | return fmt::format(fg(fmt::color::dim_gray), "{}", CLI::Formatter::make_option_desc(opt)); 27 | } 28 | }; -------------------------------------------------------------------------------- /src/colormap.h: -------------------------------------------------------------------------------- 1 | /** 2 | \file colormap.h 3 | */ 4 | #pragma once 5 | 6 | #include "fwd.h" 7 | #include "implot.h" 8 | #include 9 | 10 | using Colormap_ = int; 11 | enum EColormap : Colormap_ 12 | { 13 | Colormap_Deep = ImPlotColormap_Deep, // a.k.a. seaborn deep (qual=true, n=10) (default) 14 | Colormap_Dark = ImPlotColormap_Dark, // a.k.a. matplotlib "Set1" (qual=true, n=9 ) 15 | Colormap_Pastel = ImPlotColormap_Pastel, // a.k.a. matplotlib "Pastel1" (qual=true, n=9 ) 16 | Colormap_Paired = ImPlotColormap_Paired, // a.k.a. matplotlib "Paired" (qual=true, n=12) 17 | Colormap_Viridis = ImPlotColormap_Viridis, // a.k.a. matplotlib "viridis" (qual=false, n=11) 18 | Colormap_Plasma = ImPlotColormap_Plasma, // a.k.a. matplotlib "plasma" (qual=false, n=11) 19 | Colormap_Hot = ImPlotColormap_Hot, // a.k.a. matplotlib/MATLAB "hot" (qual=false, n=11) 20 | Colormap_Cool = ImPlotColormap_Cool, // a.k.a. matplotlib/MATLAB "cool" (qual=false, n=11) 21 | Colormap_Pink = ImPlotColormap_Pink, // a.k.a. matplotlib/MATLAB "pink" (qual=false, n=11) 22 | Colormap_Jet = ImPlotColormap_Jet, // a.k.a. MATLAB "jet" (qual=false, n=11) 23 | Colormap_Twilight = ImPlotColormap_Twilight, // a.k.a. matplotlib "twilight" (qual=false, n=11) 24 | Colormap_RdBu = ImPlotColormap_RdBu, // red/blue, Color Brewer (qual=false, n=11) 25 | Colormap_BrBG = ImPlotColormap_BrBG, // brown/blue-green, Color Brewer (qual=false, n=11) 26 | Colormap_PiYG = ImPlotColormap_PiYG, // pink/yellow-green, Color Brewer (qual=false, n=11) 27 | Colormap_Spectral = ImPlotColormap_Spectral, // color spectrum, Color Brewer (qual=false, n=11) 28 | Colormap_Greys = ImPlotColormap_Greys, // white/black (qual=false, n=2 ) 29 | Colormap_Inferno, 30 | Colormap_Turbo, 31 | Colormap_IceFire, 32 | Colormap_CoolWarm, 33 | Colormap_COUNT 34 | }; 35 | 36 | class Colormap 37 | { 38 | public: 39 | static void initialize(); 40 | static void cleanup(); 41 | 42 | static const char *name(Colormap_ idx); 43 | static Texture *texture(Colormap_ idx); 44 | static const std::vector &values(Colormap_ idx); 45 | }; 46 | -------------------------------------------------------------------------------- /src/common.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Wojciech Jarosz. All rights reserved. 3 | // Use of this source code is governed by a BSD-style license that can 4 | // be found in the LICENSE.txt file. 5 | // 6 | 7 | #include "common.h" 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | using namespace std; 14 | 15 | bool starts_with(const string &s, const string &prefix) { return s.rfind(prefix, 0) == 0; } 16 | bool ends_with(const string &s, const string &suffix) 17 | { 18 | return s.find(suffix, s.length() - suffix.length()) != string::npos; 19 | } 20 | 21 | string get_extension(const string &path) 22 | { 23 | if (auto last_dot = path.find_last_of("."); last_dot != string::npos) 24 | return path.substr(last_dot + 1); 25 | return ""; 26 | } 27 | 28 | string get_filename(const string &path) 29 | { 30 | if (auto last_slash = path.find_last_of("/\\"); last_slash != string::npos) 31 | return path.substr(last_slash + 1); 32 | return path; 33 | } 34 | 35 | string get_basename(const string &path) 36 | { 37 | auto last_slash = path.find_last_of("/\\"); 38 | auto last_dot = path.find_last_of("."); 39 | if (last_slash == string::npos && last_dot == string::npos) 40 | return path; 41 | 42 | auto start = (last_slash != string::npos) ? last_slash + 1 : 0; 43 | auto length = (last_dot != string::npos) ? last_dot - start : path.size() - start; 44 | return path.substr(start, length); 45 | } 46 | 47 | string to_lower(string str) 48 | { 49 | transform(begin(str), end(str), begin(str), [](unsigned char c) { return (char)tolower(c); }); 50 | return str; 51 | } 52 | 53 | string to_upper(string str) 54 | { 55 | transform(begin(str), end(str), begin(str), [](unsigned char c) { return (char)toupper(c); }); 56 | return str; 57 | } 58 | 59 | void process_lines(string_view input, function op) 60 | { 61 | istringstream iss(input.data()); 62 | for (string line; getline(iss, line);) op(line); 63 | } 64 | 65 | string add_line_numbers(string_view input) 66 | { 67 | istringstream iss(input.data()); 68 | ostringstream oss; 69 | size_t line_number = 1; 70 | 71 | // Calculate the number of digits in the total number of lines 72 | size_t total_lines = std::count(input.begin(), input.end(), '\n') + 1; 73 | size_t line_digits = (total_lines == 0) ? 1 : static_cast(std::log10(total_lines)) + 1; 74 | 75 | for (string line; getline(iss, line);) 76 | { 77 | // Prepend the line number with padding 78 | oss << std::setw(line_digits) << std::setfill(' ') << line_number << ": " << line << endl; 79 | line_number++; 80 | } 81 | 82 | return oss.str(); 83 | } 84 | 85 | string indent(string_view input, bool also_indent_first, int amount) 86 | { 87 | istringstream iss(input.data()); 88 | ostringstream oss; 89 | string spacer(amount, ' '); 90 | bool first_line = !also_indent_first; 91 | for (string line; getline(iss, line);) 92 | { 93 | if (!first_line) 94 | oss << spacer; 95 | oss << line; 96 | if (!iss.eof()) 97 | oss << endl; 98 | first_line = false; 99 | } 100 | return oss.str(); 101 | } 102 | 103 | const vector &tonemap_names() 104 | { 105 | static const vector names{"sRGB", "Gamma", "False color", "Positive/Negative"}; 106 | return names; 107 | } 108 | 109 | const vector &channel_names() 110 | { 111 | static const vector names{"RGBA", "Red", "Green", "Blue", "Alpha", "Luminance"}; 112 | return names; 113 | } 114 | 115 | const vector &blend_mode_names() 116 | { 117 | static const vector names{ 118 | "Normal", "Multiply", "Divide", "Add", "Average", "Subtract", "Difference", "Relative difference", 119 | }; 120 | return names; 121 | } 122 | 123 | string channel_to_string(EChannel channel) { return channel_names()[channel]; } 124 | 125 | string blend_mode_to_string(EBlendMode mode) { return blend_mode_names()[mode]; } 126 | 127 | static inline int code_point_length(char first) 128 | { 129 | if ((first & 0xf8) == 0xf0) 130 | return 4; 131 | else if ((first & 0xf0) == 0xe0) 132 | return 3; 133 | else if ((first & 0xe0) == 0xc0) 134 | return 2; 135 | else 136 | return 1; 137 | } 138 | 139 | // This function is adapted from tev: 140 | // This file was developed by Thomas Müller . 141 | // It is published under the BSD 3-Clause License within the LICENSE file. 142 | pair find_common_prefix_suffix(const vector &names) 143 | { 144 | int begin_short_offset = 0; 145 | int end_short_offset = 0; 146 | if (!names.empty()) 147 | { 148 | string first = names.front(); 149 | int first_size = (int)first.size(); 150 | if (first_size > 0) 151 | { 152 | bool all_start_with_same_char = false; 153 | do { 154 | int len = code_point_length(first[begin_short_offset]); 155 | 156 | all_start_with_same_char = 157 | all_of(begin(names), end(names), 158 | [&first, begin_short_offset, len](const string &name) 159 | { 160 | if (begin_short_offset + len > (int)name.size()) 161 | return false; 162 | 163 | for (int i = begin_short_offset; i < begin_short_offset + len; ++i) 164 | if (name[i] != first[i]) 165 | return false; 166 | 167 | return true; 168 | }); 169 | 170 | if (all_start_with_same_char) 171 | begin_short_offset += len; 172 | } while (all_start_with_same_char && begin_short_offset < first_size); 173 | 174 | bool all_end_with_same_char; 175 | do { 176 | char last_char = first[first_size - end_short_offset - 1]; 177 | all_end_with_same_char = all_of(begin(names), end(names), 178 | [last_char, end_short_offset](const string &name) 179 | { 180 | int index = (int)name.size() - end_short_offset - 1; 181 | return index >= 0 && name[index] == last_char; 182 | }); 183 | 184 | if (all_end_with_same_char) 185 | ++end_short_offset; 186 | } while (all_end_with_same_char && end_short_offset < first_size); 187 | } 188 | } 189 | return {begin_short_offset, end_short_offset}; 190 | } 191 | 192 | pair human_readable_size(size_t bytes) 193 | { 194 | float size = static_cast(bytes); 195 | static const char *units[] = {"B", "kB", "MiB", "GiB", "TiB", "PiB"}; 196 | int unit_index = 0; 197 | 198 | while (size >= 1024 && unit_index < 5) 199 | { 200 | size /= 1024; 201 | ++unit_index; 202 | } 203 | 204 | return {size, units[unit_index]}; 205 | } -------------------------------------------------------------------------------- /src/emscripten_utils.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include "emscripten_utils.h" 3 | #include "app.h" 4 | #include "common.h" 5 | #include "imgui.h" 6 | 7 | #include 8 | 9 | #ifdef __EMSCRIPTEN__ 10 | #include 11 | 12 | EM_JS(bool, isSafari, (), { 13 | var is_safari = /^((?!chrome|android).)*safari/i.test(navigator.userAgent); 14 | return is_safari; 15 | }); 16 | EM_JS(bool, isAppleDevice, (), { 17 | const ua = navigator.userAgent; 18 | return (ua.includes("Macintosh") || ua.includes("iPad") || ua.includes("iPhone") || ua.includes("iPod")); 19 | }); 20 | 21 | static std::string g_clipboard_content; // this stores the content for our internal clipboard 22 | 23 | static char const *get_clipboard_for_imgui(ImGuiContext *user_data [[maybe_unused]]) 24 | { 25 | /// Callback for imgui, to return clipboard content 26 | spdlog::info("ImGui requested clipboard content, returning '{}'", g_clipboard_content); 27 | return g_clipboard_content.c_str(); 28 | } 29 | 30 | static void set_clipboard_from_imgui(ImGuiContext *user_data [[maybe_unused]], char const *text) 31 | { 32 | /// Callback for imgui, to set clipboard content 33 | g_clipboard_content = text; 34 | spdlog::info("ImGui setting clipboard content to '{}'", g_clipboard_content); 35 | emscripten_browser_clipboard::copy(g_clipboard_content); // send clipboard data to the browser 36 | } 37 | #endif 38 | 39 | void setup_imgui_clipboard() 40 | { 41 | #ifdef __EMSCRIPTEN__ 42 | // spdlog::info("Setting up paste callback"); 43 | // emscripten_browser_clipboard::paste( 44 | // [](std::string &&paste_data, void *) 45 | // { 46 | // /// Callback to handle clipboard paste from browser 47 | // spdlog::info("Browser pasted: '{}'", paste_data); 48 | // g_clipboard_content = std::move(paste_data); 49 | // }); 50 | ImGui::GetPlatformIO().Platform_SetClipboardTextFn = set_clipboard_from_imgui; 51 | ImGui::GetPlatformIO().Platform_GetClipboardTextFn = get_clipboard_for_imgui; 52 | #endif 53 | } 54 | 55 | bool host_is_apple() 56 | { 57 | #if defined(__EMSCRIPTEN__) 58 | return isAppleDevice(); 59 | #elif defined(__APPLE__) 60 | return true; 61 | #else 62 | return false; 63 | #endif 64 | } 65 | bool host_is_safari() 66 | { 67 | #if defined(__EMSCRIPTEN__) 68 | return isSafari(); 69 | #else 70 | return false; 71 | #endif 72 | } 73 | 74 | #if defined(__EMSCRIPTEN__) 75 | //------------------------------------------------------------------------------ 76 | // Javascript interface functions 77 | // 78 | extern "C" 79 | { 80 | 81 | EMSCRIPTEN_KEEPALIVE int hdrview_loadfile(const char *filename, const char *buffer, size_t buffer_size) 82 | { 83 | auto [size, unit] = human_readable_size(buffer_size); 84 | spdlog::info("User dropped a {:.0f} {} file with filename '{}'", size, unit, filename); 85 | 86 | if (!buffer || buffer_size == 0) 87 | { 88 | spdlog::warn("Empty file, skipping..."); 89 | return 1; 90 | } 91 | else 92 | { 93 | hdrview()->load_image(filename, {buffer, buffer_size}); 94 | return 0; 95 | } 96 | } 97 | 98 | } // extern "C" 99 | 100 | #endif -------------------------------------------------------------------------------- /src/emscripten_utils.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | void setup_imgui_clipboard(); 4 | bool host_is_apple(); 5 | bool host_is_safari(); 6 | 7 | #ifdef __EMSCRIPTEN__ 8 | #include 9 | 10 | extern "C" 11 | { 12 | EMSCRIPTEN_KEEPALIVE int hdrview_loadfile(const char *filename, const char *buffer, size_t buffer_size); 13 | } // extern "C" 14 | 15 | #endif 16 | -------------------------------------------------------------------------------- /src/fonts.cpp: -------------------------------------------------------------------------------- 1 | #include "fonts.h" 2 | #include "app.h" 3 | #include "timer.h" 4 | 5 | using namespace std; 6 | 7 | void HDRViewApp::load_fonts() 8 | { 9 | Timer timer; 10 | spdlog::info("Loading fonts..."); 11 | auto load_font = [](const string &font_path, int size) 12 | { 13 | if (!HelloImGui::AssetExists(font_path)) 14 | spdlog::critical("Cannot find the font asset '{}'!"); 15 | 16 | return HelloImGui::LoadFont(font_path, (float)size); 17 | }; 18 | 19 | auto append_icon_font = [](const string &path, float size, const vector &glyphRanges) 20 | { 21 | if (HelloImGui::AssetExists(path)) 22 | { 23 | HelloImGui::FontLoadingParams iconFontParams; 24 | iconFontParams.mergeToLastFont = true; 25 | iconFontParams.fontConfig.PixelSnapH = true; 26 | iconFontParams.useFullGlyphRange = false; 27 | iconFontParams.glyphRanges = glyphRanges; 28 | 29 | #if defined(HDRVIEW_ICONSET_FA6) 30 | auto icon_font_size = 0.85f * size; 31 | iconFontParams.fontConfig.GlyphMinAdvanceX = iconFontParams.fontConfig.GlyphMaxAdvanceX = 32 | icon_font_size * HelloImGui::DpiFontLoadingFactor() * 1.25f; 33 | iconFontParams.fontConfig.GlyphOffset.x = icon_font_size * HelloImGui::DpiFontLoadingFactor() * 0.05f; 34 | #elif defined(HDRVIEW_ICONSET_LC) 35 | auto icon_font_size = size; 36 | iconFontParams.fontConfig.GlyphOffset.x = icon_font_size * HelloImGui::DpiFontLoadingFactor() * 0.03f; 37 | iconFontParams.fontConfig.GlyphOffset.y = icon_font_size * HelloImGui::DpiFontLoadingFactor() * 0.20f; 38 | #elif defined(HDRVIEW_ICONSET_MS) 39 | auto icon_font_size = 1.28571429f * size; 40 | iconFontParams.fontConfig.GlyphOffset.x = icon_font_size * HelloImGui::DpiFontLoadingFactor() * 0.01f; 41 | iconFontParams.fontConfig.GlyphOffset.y = icon_font_size * HelloImGui::DpiFontLoadingFactor() * 0.2f; 42 | #elif defined(HDRVIEW_ICONSET_MD) 43 | auto icon_font_size = size; 44 | iconFontParams.fontConfig.GlyphOffset.x = icon_font_size * HelloImGui::DpiFontLoadingFactor() * 0.01f; 45 | iconFontParams.fontConfig.GlyphOffset.y = icon_font_size * HelloImGui::DpiFontLoadingFactor() * 0.2f; 46 | #elif defined(HDRVIEW_ICONSET_MDI) 47 | auto icon_font_size = size; 48 | iconFontParams.fontConfig.GlyphOffset = 49 | icon_font_size * HelloImGui::DpiFontLoadingFactor() * float2{0.02f, 0.1f}; 50 | 51 | #endif 52 | HelloImGui::LoadFont(path, icon_font_size, iconFontParams); 53 | } 54 | else 55 | spdlog::critical("Cannot find the icon font '{}'", path); 56 | }; 57 | 58 | vector glyphRanges = {}; 59 | { 60 | // load all icon glyphs 61 | // glyphRanges.push_back({ICON_MIN_MY, ICON_MAX_MY}); 62 | 63 | // only load the icon glyphs we use 64 | ImVector ranges; 65 | ImFontGlyphRangesBuilder builder; 66 | 67 | builder.AddText(ICON_MY_OPEN_IMAGE); 68 | builder.AddText(ICON_MY_ABOUT); 69 | builder.AddText(ICON_MY_FIT_AXES); 70 | builder.AddText(ICON_MY_MANUAL_AXES); 71 | builder.AddText(ICON_MY_LIST_OL); 72 | builder.AddText(ICON_MY_VISIBILITY); 73 | builder.AddText(ICON_MY_VISIBILITY_OFF); 74 | builder.AddText(ICON_MY_KEY_CONTROL); 75 | builder.AddText(ICON_MY_KEY_COMMAND); 76 | builder.AddText(ICON_MY_KEY_OPTION); 77 | builder.AddText(ICON_MY_KEY_SHIFT); 78 | builder.AddText(ICON_MY_CHANNEL_GROUP); 79 | builder.AddText(ICON_MY_NO_CHANNEL_GROUP); 80 | builder.AddText(ICON_MY_QUIT); 81 | builder.AddText(ICON_MY_COMMAND_PALETTE); 82 | builder.AddText(ICON_MY_TWEAK_THEME); 83 | builder.AddText(ICON_MY_SHOW_ALL_WINDOWS); 84 | builder.AddText(ICON_MY_HIDE_ALL_WINDOWS); 85 | builder.AddText(ICON_MY_RESTORE_LAYOUT); 86 | builder.AddText(ICON_MY_EXPOSURE); 87 | builder.AddText(ICON_MY_REDUCE_EXPOSURE); 88 | builder.AddText(ICON_MY_INCREASE_EXPOSURE); 89 | builder.AddText(ICON_MY_RESET_TONEMAPPING); 90 | builder.AddText(ICON_MY_NORMALIZE_EXPOSURE); 91 | builder.AddText(ICON_MY_DITHER); 92 | builder.AddText(ICON_MY_CLAMP_TO_LDR); 93 | builder.AddText(ICON_MY_SHOW_GRID); 94 | builder.AddText(ICON_MY_SAVE_AS); 95 | builder.AddText(ICON_MY_CLOSE); 96 | builder.AddText(ICON_MY_CLOSE_ALL); 97 | builder.AddText(ICON_MY_ZOOM_OUT); 98 | builder.AddText(ICON_MY_ZOOM_IN); 99 | builder.AddText(ICON_MY_ZOOM_100); 100 | builder.AddText(ICON_MY_FIT_TO_WINDOW); 101 | builder.AddText(ICON_MY_CENTER); 102 | builder.AddText(ICON_MY_FILTER); 103 | builder.AddText(ICON_MY_DELETE); 104 | builder.AddText(ICON_MY_IMAGE); 105 | builder.AddText(ICON_MY_IMAGES); 106 | builder.AddText(ICON_MY_REFERENCE_IMAGE); 107 | builder.AddText(ICON_MY_THEME); 108 | builder.AddText(ICON_MY_ARROW_UP); 109 | builder.AddText(ICON_MY_ARROW_DOWN); 110 | builder.AddText(ICON_MY_KEY_RETURN); 111 | builder.AddText(ICON_MY_KEY_ESC); 112 | builder.AddText(ICON_MY_LOG_LEVEL); 113 | builder.AddText(ICON_MY_LOG_LEVEL_TRACE); 114 | builder.AddText(ICON_MY_LOG_LEVEL_DEBUG); 115 | builder.AddText(ICON_MY_LOG_LEVEL_INFO); 116 | builder.AddText(ICON_MY_LOG_LEVEL_WARN); 117 | builder.AddText(ICON_MY_LOG_LEVEL_ERROR); 118 | builder.AddText(ICON_MY_LOG_LEVEL_CRITICAL); 119 | builder.AddText(ICON_MY_LOG_LEVEL_OFF); 120 | builder.AddText(ICON_MY_GREATER_EQUAL); 121 | builder.AddText(ICON_MY_TRASH_CAN); 122 | builder.AddText(ICON_MY_LOCK); 123 | builder.AddText(ICON_MY_LOCK_OPEN); 124 | builder.AddText(ICON_MY_TEXT_WRAP_ON); 125 | builder.AddText(ICON_MY_TEXT_WRAP_OFF); 126 | builder.AddText(ICON_MY_WIDEST); 127 | builder.AddText(ICON_MY_LINK); 128 | builder.AddText(ICON_MY_TOOLBAR); 129 | builder.AddText(ICON_MY_STATUSBAR); 130 | builder.AddText(ICON_MY_HIDE_GUI); 131 | builder.AddText(ICON_MY_FPS); 132 | builder.AddText(ICON_MY_DISPLAY_WINDOW); 133 | builder.AddText(ICON_MY_DATA_WINDOW); 134 | builder.AddText(ICON_MY_LIST_VIEW); 135 | builder.AddText(ICON_MY_TREE_VIEW); 136 | builder.AddText(ICON_MY_SHORT_NAMES); 137 | builder.AddText(ICON_MY_FULL_NAMES); 138 | builder.AddText(ICON_MY_SHOW_PIXEL_VALUES); 139 | builder.AddText(ICON_MY_HOVERED_PIXEL); 140 | builder.AddText(ICON_MY_WATCHED_PIXEL); 141 | builder.AddText(ICON_MY_ARROW_DROP_DOWN); 142 | builder.AddText(ICON_MY_CURSOR_ARROW); 143 | builder.AddText(ICON_MY_TIMES); 144 | builder.AddText(ICON_MY_SELECT); 145 | builder.AddText(ICON_MY_PAN_ZOOM_TOOL); 146 | 147 | // Build the final result (ordered ranges with all the unique characters submitted) 148 | builder.BuildRanges(&ranges); 149 | // spdlog::info("Icon font ranges: {}", ranges.size()); 150 | for (int i = 0; i < ranges.size() - 1; i += 2) glyphRanges.push_back({ranges[i], ranges[i + 1]}); 151 | } 152 | 153 | // default font size gets icons 154 | for (auto font_size : {14, 10, 16, 18, 30}) 155 | { 156 | m_fonts[std::pair{"sans regular", font_size}] = 157 | load_font("fonts/Roboto/Roboto-Regular.ttf", font_size); 158 | append_icon_font(FONT_ICON_FILE_NAME_MY, (float)font_size, glyphRanges); 159 | 160 | m_fonts[std::pair{"sans bold", font_size}] = load_font("fonts/Roboto/Roboto-Bold.ttf", font_size); 161 | append_icon_font(FONT_ICON_FILE_NAME_MY, (float)font_size, glyphRanges); 162 | 163 | m_fonts[std::pair{"mono regular", font_size}] = 164 | load_font("fonts/Roboto/RobotoMono-Regular.ttf", font_size); 165 | append_icon_font(FONT_ICON_FILE_NAME_MY, (float)font_size, glyphRanges); 166 | 167 | m_fonts[std::pair{"mono bold", font_size}] = 168 | load_font("fonts/Roboto/RobotoMono-Bold.ttf", font_size); 169 | append_icon_font(FONT_ICON_FILE_NAME_MY, (float)font_size, glyphRanges); 170 | } 171 | // spdlog::info("\ttook {} seconds.", (timer.elapsed() / 1000.f)); 172 | } -------------------------------------------------------------------------------- /src/fwd.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Wojciech Jarosz. All rights reserved. 3 | // Use of this source code is governed by a BSD-style license that can 4 | // be found in the LICENSE.txt file. 5 | // 6 | 7 | #pragma once 8 | 9 | #include "linalg.h" 10 | #include 11 | using namespace linalg::aliases; 12 | 13 | // define extra conversion here before including imgui 14 | #define IM_VEC2_CLASS_EXTRA \ 15 | constexpr ImVec2(const float2 &f) : x(f.x), y(f.y) {} \ 16 | operator float2() const { return float2(x, y); } \ 17 | constexpr ImVec2(const int2 &i) : x(i.x), y(i.y) {} \ 18 | operator int2() const { return int2((int)x, (int)y); } 19 | 20 | #define IM_VEC4_CLASS_EXTRA \ 21 | constexpr ImVec4(const float4 &f) : x(f.x), y(f.y), z(f.z), w(f.w) {} \ 22 | operator float4() const { return float4(x, y, z, w); } 23 | 24 | // Shortname for the linalg namespace 25 | namespace la = linalg; 26 | 27 | // forward declarations 28 | template , 29 | size_t Dims_ = linalg::detail::apply::size> 30 | class Box; 31 | using Color3 = float3; 32 | using Color4 = float4; 33 | 34 | // define some common types 35 | using Box1f = Box; 36 | using Box1d = Box; 37 | using Box1i = Box; 38 | 39 | using Box2f = Box; 40 | using Box2d = Box; 41 | using Box2i = Box; 42 | 43 | using Box3f = Box; 44 | using Box3d = Box; 45 | using Box3i = Box; 46 | 47 | using Box4f = Box; 48 | using Box4d = Box; 49 | using Box4i = Box; 50 | 51 | class Shader; 52 | class Texture; 53 | struct Image; 54 | class Texture; 55 | class Timer; 56 | 57 | using ConstImagePtr = std::shared_ptr; 58 | using ImagePtr = std::shared_ptr; 59 | 60 | enum EChannel : int 61 | { 62 | RGB = 0, 63 | RED, 64 | GREEN, 65 | BLUE, 66 | ALPHA, 67 | Y, 68 | 69 | NUM_CHANNELS 70 | }; 71 | 72 | using Tonemap_ = int; 73 | enum Tonemap : Tonemap_ 74 | { 75 | Tonemap_sRGB = 0, 76 | Tonemap_Gamma, 77 | Tonemap_FalseColor, 78 | Tonemap_PositiveNegative, 79 | 80 | Tonemap_COUNT 81 | }; 82 | 83 | enum EBlendMode : int 84 | { 85 | NORMAL_BLEND = 0, 86 | MULTIPLY_BLEND, 87 | DIVIDE_BLEND, 88 | ADD_BLEND, 89 | AVERAGE_BLEND, 90 | SUBTRACT_BLEND, 91 | DIFFERENCE_BLEND, 92 | RELATIVE_DIFFERENCE_BLEND, 93 | 94 | NUM_BLEND_MODES 95 | }; 96 | 97 | enum EBGMode : int 98 | { 99 | BG_BLACK = 0, 100 | BG_WHITE, 101 | BG_DARK_CHECKER, 102 | BG_LIGHT_CHECKER, 103 | BG_CUSTOM_COLOR, 104 | 105 | NUM_BG_MODES 106 | }; 107 | 108 | enum EDirection 109 | { 110 | Forward, 111 | Backward, 112 | }; 113 | 114 | using AxisScale_ = int; 115 | enum AxisScale : AxisScale_ 116 | { 117 | AxisScale_Linear = 0, 118 | AxisScale_SRGB, 119 | AxisScale_Asinh, 120 | AxisScale_SymLog, 121 | 122 | AxisScale_COUNT 123 | }; 124 | 125 | using Target_ = int; 126 | enum Target : Target_ 127 | { 128 | Target_Primary = 0, 129 | Target_Secondary = 1, 130 | 131 | Target_COUNT 132 | }; 133 | 134 | using MouseMode_ = int; 135 | enum MouseMode : MouseMode_ 136 | { 137 | MouseMode_PanZoom = 0, 138 | MouseMode_ColorInspector, 139 | MouseMode_RectangularSelection, 140 | 141 | MouseMode_COUNT 142 | }; 143 | -------------------------------------------------------------------------------- /src/hdrview.cpp: -------------------------------------------------------------------------------- 1 | /** \file hdrview.cpp 2 | \author Wojciech Jarosz 3 | */ 4 | 5 | #include "app.h" 6 | #include "cliformatter.h" 7 | #include "imgui_ext.h" 8 | #include "version.h" 9 | #include 10 | #include 11 | #include 12 | 13 | #ifdef _WIN32 14 | #include 15 | #include 16 | #endif 17 | 18 | int main(int argc, char **argv) 19 | { 20 | #ifdef _WIN32 21 | // Manually get the command line arguments, since we are not compiling in console mode 22 | LPWSTR cmd_line = GetCommandLineW(); 23 | wchar_t **win_argv = CommandLineToArgvW(cmd_line, &argc); 24 | 25 | std::vector args; 26 | args.reserve(static_cast(argc) - 1U); 27 | for (auto i = static_cast(argc) - 1U; i > 0U; --i) args.emplace_back(CLI::narrow(win_argv[i])); 28 | 29 | // This will enable console output if the app was started from a console. No extra console window is created if the 30 | // app was started any other way. 31 | bool consoleAvailable = AttachConsole(ATTACH_PARENT_PROCESS); 32 | 33 | #ifndef NDEBUG 34 | // In Debug mode we create a console even if launched from within, e.g., Visual Studio 35 | bool customConsole = false; 36 | if (!consoleAvailable) 37 | { 38 | consoleAvailable = AllocConsole(); 39 | customConsole = consoleAvailable; 40 | } 41 | #endif 42 | 43 | FILE *con_out = nullptr, *con_err = nullptr, *con_in = nullptr; 44 | if (consoleAvailable) 45 | { 46 | freopen_s(&con_out, "CONOUT$", "w", stdout); 47 | freopen_s(&con_err, "CONOUT$", "w", stderr); 48 | freopen_s(&con_in, "CONIN$", "r", stdin); 49 | std::cout.clear(); 50 | std::clog.clear(); 51 | std::cerr.clear(); 52 | std::cin.clear(); 53 | } 54 | #endif 55 | 56 | #ifdef ASSETS_LOCATION 57 | HelloImGui::SetAssetsFolder(ASSETS_LOCATION); 58 | #endif 59 | 60 | constexpr spdlog::level::level_enum default_verbosity = spdlog::level::info; 61 | int verbosity = default_verbosity; 62 | 63 | std::optional exposure, gamma; 64 | std::optional dither, force_sdr, apple_keys; 65 | string url; 66 | 67 | vector in_files; 68 | 69 | try 70 | { 71 | string version_string = 72 | fmt::format("HDRView {}. (built using {} backend on {})", version(), backend(), build_timestamp()); 73 | 74 | CLI::App app{R"(HDRView is a simple research-oriented tool for examining, 75 | comparing, and converting high-dynamic range images. HDRView 76 | is freely available under a 3-clause BSD license. 77 | )", 78 | "HDRView"}; 79 | 80 | app.formatter(std::make_shared()); 81 | app.get_formatter()->column_width(20); 82 | app.get_formatter()->label("OPTIONS", 83 | fmt::format(fmt::emphasis::bold | fg(fmt::color::cornflower_blue), "OPTIONS")); 84 | 85 | app.add_option("-e,--exposure", exposure, 86 | R"(Desired power of 2 EV or exposure value (gain = 2^exposure) 87 | [default: 0].)") 88 | ->capture_default_str() 89 | ->group("Tone mapping and display"); 90 | 91 | app.add_option("-g,--gamma", gamma, 92 | R"(Desired gamma value for exposure+gamma tonemapping. An 93 | sRGB curve is used if gamma is not specified.)") 94 | ->group("Tone mapping and display"); 95 | 96 | app.add_flag("--dither,--no-dither{false}", dither, 97 | "Enable/disable dithering when converting to LDR\n[default: on].") 98 | ->group("Tone mapping and display"); 99 | 100 | app.add_flag("--sdr", force_sdr, "Force standard dynamic range (8-bit per channel) display.") 101 | ->group("Tone mapping and display"); 102 | 103 | app.add_option("-v,--verbosity", verbosity, 104 | R"(Set verbosity threshold T with lower values meaning more 105 | verbose and higher values removing low-priority messages. 106 | All messages with severity >= T are displayed, where the 107 | severities are: 108 | trace = 0 109 | debug = 1 110 | info = 2 111 | warn = 3 112 | err = 4 113 | critical = 5 114 | off = 6 115 | The default is 2 (info).)") 116 | ->check(CLI::Range(0, 6)) 117 | ->option_text("INT in [0-6]") 118 | ->group("Misc"); 119 | 120 | app.add_flag("--apple-keys,--non-apple-keys{false}", apple_keys, 121 | "Apple-style keyboard behavior (Cmd key instead of Ctrl, etc.)") 122 | ->group("Misc"); 123 | 124 | app.set_version_flag("--version", version_string, "Show the version and exit.")->group("Misc"); 125 | 126 | app.set_help_flag("-h, --help", "Print this help message and exit.")->group("Misc"); 127 | 128 | app.add_option("IMAGES", in_files, "The image files to load.") 129 | ->check(CLI::ExistingPath) 130 | ->option_text("PATH(existing) ..."); 131 | #if defined(__EMSCRIPTEN__) 132 | app.add_option("--url", url, "URL of an image to download and open"); 133 | #endif 134 | 135 | // enable all log messages globally, and for the default logger 136 | spdlog::set_level(spdlog::level::trace); 137 | spdlog::default_logger()->set_level(spdlog::level::trace); 138 | spdlog::set_pattern("%^[%T | %5l]: %$%v"); 139 | 140 | // create a new sink. 141 | // new sinks default to allowing all levels (that are propagated by the parent logger) 142 | spdlog::default_logger()->sinks().push_back(ImGui::GlobalSpdLogWindow().sink()); 143 | // spdlog::info("There are {} sinks", spdlog::default_logger()->sinks().size()); 144 | ImGui::GlobalSpdLogWindow().set_pattern("%^%*[%T | %5l]: %$%v"); 145 | 146 | #ifdef _WIN32 147 | (void)argv; // prevent unused variable warning 148 | CLI11_PARSE(app, argc, win_argv); 149 | #else 150 | CLI11_PARSE(app, argc, argv); 151 | #endif 152 | 153 | spdlog::default_logger()->sinks().front()->set_level(spdlog::level::level_enum(verbosity)); 154 | ImGui::GlobalSpdLogWindow().sink()->set_level(spdlog::level::level_enum(verbosity)); 155 | 156 | if (argc > 1) 157 | { 158 | spdlog::debug("Launching with command line arguments:"); 159 | for (int i = 1; i < argc; ++i) 160 | #ifdef _WIN32 161 | spdlog::debug("\t" + string(CLI::narrow(win_argv[i]))); 162 | #else 163 | spdlog::debug("\t" + string(argv[i])); 164 | #endif 165 | } 166 | else 167 | spdlog::debug("Launching HDRView with no command line arguments."); 168 | 169 | spdlog::info("Welcome to HDRView!"); 170 | spdlog::info("Verbosity threshold set to level {:d}.", verbosity); 171 | if (exposure.has_value()) 172 | spdlog::info("Forcing exposure to {:f} (intensity scale of {:f})", *exposure, powf(2.0f, *exposure)); 173 | 174 | // gamma or sRGB 175 | if (gamma.has_value()) 176 | spdlog::info("Forcing gamma correction to g={:f}.", *gamma); 177 | else 178 | spdlog::info("Using sRGB response curve."); 179 | 180 | // dithering 181 | if (dither.has_value()) 182 | spdlog::info("Forcing dithering {}.", *dither ? "ON" : "OFF"); 183 | 184 | if (apple_keys.has_value()) 185 | spdlog::info("Turning Apple-style keyboard behavior {}.", *apple_keys ? "ON" : "OFF"); 186 | 187 | init_hdrview(exposure, gamma, dither, force_sdr, apple_keys, in_files); 188 | hdrview()->load_url(url); 189 | hdrview()->run(); 190 | } 191 | // Exceptions will only be thrown upon failed logger or sink construction (not during logging) 192 | catch (const spdlog::spdlog_ex &e) 193 | { 194 | fprintf(stderr, "Log init failed: %s\n", e.what()); 195 | exit(EXIT_FAILURE); 196 | } 197 | catch (const std::exception &e) 198 | { 199 | fprintf(stderr, "Error: %s\n", e.what()); 200 | // fprintf(stderr, "%s", USAGE); 201 | exit(EXIT_FAILURE); 202 | } 203 | 204 | #ifdef _WIN32 205 | if (consoleAvailable) 206 | { 207 | #ifndef NDEBUG 208 | if (customConsole) 209 | FreeConsole(); 210 | #endif 211 | 212 | fclose(con_out); 213 | fclose(con_err); 214 | fclose(con_in); 215 | } 216 | #endif 217 | 218 | return EXIT_SUCCESS; 219 | } 220 | -------------------------------------------------------------------------------- /src/imageio/exr.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Wojciech Jarosz. All rights reserved. 3 | // Use of this source code is governed by a BSD-style license that can 4 | // be found in the LICENSE.txt file. 5 | // 6 | 7 | #include "colorspace.h" 8 | #include "common.h" // for lerp, mod, clamp, getExtension 9 | #include "exr_std_streams.h" 10 | #include "image.h" 11 | #include "texture.h" 12 | #include "timer.h" 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include // for isOpenExrFile 22 | #include 23 | #include // for runtime_error, out_of_range 24 | 25 | #include "Imath_to_linalg.h" 26 | 27 | using namespace std; 28 | 29 | bool is_exr_image(istream &is_, const string &filename) noexcept 30 | { 31 | auto is = StdIStream{is_, filename.c_str()}; 32 | return Imf::isOpenExrFile(is); 33 | } 34 | 35 | vector load_exr_image(istream &is_, const string &filename) 36 | { 37 | auto is = StdIStream{is_, filename.c_str()}; 38 | 39 | Imf::MultiPartInputFile infile{is}; 40 | 41 | if (infile.parts() <= 0) 42 | throw invalid_argument{"EXR file contains no parts!"}; 43 | 44 | vector images; 45 | for (int p = 0; p < infile.parts(); ++p) 46 | { 47 | Imf::InputPart part{infile, p}; 48 | 49 | Imath::Box2i dataWindow = part.header().dataWindow(); 50 | Imath::Box2i displayWindow = part.header().displayWindow(); 51 | int2 size = {dataWindow.max.x - dataWindow.min.x + 1, dataWindow.max.y - dataWindow.min.y + 1}; 52 | 53 | if (size.x <= 0 || size.y <= 0) 54 | { 55 | spdlog::warn("EXR part {}: '{}' has zero pixels, skipping...", p, 56 | part.header().hasName() ? part.header().name() : "unnamed"); 57 | continue; 58 | } 59 | 60 | images.emplace_back(make_shared()); 61 | auto &img = *images.back(); 62 | img.header = part.header(); 63 | img.metadata["loader"] = "OpenEXR"; 64 | 65 | for (auto a = begin(img.header); a != end(img.header); ++a) spdlog::debug("Attribute: {}", a.name()); 66 | 67 | if (img.header.hasName()) 68 | img.partname = img.header.name(); 69 | 70 | // OpenEXR library's boxes include the max element, our boxes don't, so we increment by 1 71 | img.data_window = {{dataWindow.min.x, dataWindow.min.y}, {dataWindow.max.x + 1, dataWindow.max.y + 1}}; 72 | img.display_window = {{displayWindow.min.x, displayWindow.min.y}, 73 | {displayWindow.max.x + 1, displayWindow.max.y + 1}}; 74 | 75 | if (img.data_window.is_empty()) 76 | throw invalid_argument{fmt::format("EXR image has invalid data window: [{},{}] - [{},{}]", 77 | img.data_window.min.x, img.data_window.min.y, img.data_window.max.x, 78 | img.data_window.max.y)}; 79 | 80 | if (img.display_window.is_empty()) 81 | throw invalid_argument{fmt::format("EXR image has invalid display window: [{},{}] - [{},{}]", 82 | img.display_window.min.x, img.display_window.min.y, 83 | img.display_window.max.x, img.display_window.max.y)}; 84 | 85 | const auto &channels = img.header.channels(); 86 | 87 | Imf::FrameBuffer framebuffer; 88 | for (auto c = channels.begin(); c != channels.end(); ++c) 89 | { 90 | string name = c.name(); 91 | 92 | img.channels.emplace_back(name, size); 93 | framebuffer.insert(c.name(), Imf::Slice::Make(Imf::FLOAT, img.channels.back().data(), dataWindow, 0, 0, 94 | c.channel().xSampling, c.channel().ySampling)); 95 | } 96 | 97 | part.setFrameBuffer(framebuffer); 98 | part.readPixels(dataWindow.min.y, dataWindow.max.y); 99 | 100 | // now up-res any subsampled channels 101 | // FIXME: OpenEXR v3.3.0 and above seems to break this subsample channel loading 102 | // see https://github.com/AcademySoftwareFoundation/openexr/issues/1949 103 | // Until that is fixed in the next release, we are sticking with v3.2.4 104 | int i = 0; 105 | for (auto c = img.header.channels().begin(); c != img.header.channels().end(); ++c, ++i) 106 | { 107 | int xs = c.channel().xSampling; 108 | int ys = c.channel().ySampling; 109 | if (xs == 1 && ys == 1) 110 | continue; 111 | 112 | spdlog::warn("EXR channel '{}' is subsampled ({},{}). Only rudimentary subsampling is supported.", c.name(), 113 | xs, ys); 114 | Array2Df tmp = img.channels[i]; 115 | 116 | int subsampled_width = size.x / xs; 117 | for (int y = 0; y < size.y; ++y) 118 | for (int x = 0; x < size.x; ++x) img.channels[i]({x, y}) = tmp(x / xs + (y / ys) * subsampled_width); 119 | } 120 | } 121 | return images; 122 | } 123 | 124 | void save_exr_image(const Image &img, ostream &os_, const string &filename) 125 | { 126 | try 127 | { 128 | Timer timer; 129 | // OpenEXR expects the display window to be inclusive, while our images are exclusive 130 | auto displayWindow = Imath::Box2i(Imath::V2i(img.display_window.min.x, img.display_window.min.y), 131 | Imath::V2i(img.display_window.max.x - 1, img.display_window.max.y - 1)); 132 | auto dataWindow = Imath::Box2i(Imath::V2i(img.data_window.min.x, img.data_window.min.y), 133 | Imath::V2i(img.data_window.max.x - 1, img.data_window.max.y - 1)); 134 | 135 | // start with the header we have from reading in the file 136 | Imf::Header header = img.header; 137 | header.insert("channels", Imf::ChannelListAttribute()); 138 | header.displayWindow() = displayWindow; 139 | header.dataWindow() = dataWindow; 140 | 141 | Imf::FrameBuffer frameBuffer; 142 | 143 | for (int g = 0; g < (int)img.groups.size(); ++g) 144 | { 145 | auto &group = img.groups[g]; 146 | if (!group.visible) 147 | continue; 148 | 149 | for (int c = 0; c < group.num_channels; ++c) 150 | { 151 | auto &channel = img.channels[group.channels[c]]; 152 | header.channels().insert(channel.name, Imf::Channel(Imf::FLOAT)); 153 | 154 | // OpenEXR expects the base address to point to the display window origin, while our channels only store 155 | // pixels for the data_window. The Slice::Make function below does the heavy lifting of computing the 156 | // base pointer for a slice 157 | frameBuffer.insert(channel.name, Imf::Slice::Make(Imf::FLOAT, channel.data(), dataWindow)); 158 | } 159 | } 160 | 161 | auto os = StdOStream{os_, filename.c_str()}; 162 | Imf::OutputFile file{os, header}; 163 | file.setFrameBuffer(frameBuffer); 164 | file.writePixels(img.data_window.size().y); 165 | spdlog::info("Saved EXR image to \"{}\" in {} seconds.", filename, (timer.elapsed() / 1000.f)); 166 | } 167 | catch (const exception &e) 168 | { 169 | throw runtime_error{fmt::format("Failed to write EXR image \"{}\" failed: {}", filename, e.what())}; 170 | } 171 | } 172 | -------------------------------------------------------------------------------- /src/imageio/exr.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Wojciech Jarosz. All rights reserved. 3 | // Use of this source code is governed by a BSD-style license that can 4 | // be found in the LICENSE.txt file. 5 | // 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "fwd.h" 14 | 15 | // does not throw 16 | bool is_exr_image(std::istream &is, const std::string &filename) noexcept; 17 | // throws on error 18 | std::vector load_exr_image(std::istream &is, const std::string &filename); 19 | // throws on error 20 | void save_exr_image(const Image &img, std::ostream &os, const std::string &filename); -------------------------------------------------------------------------------- /src/imageio/exr_std_streams.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Wojciech Jarosz. All rights reserved. 3 | // Use of this source code is governed by a BSD-style license that can 4 | // be found in the LICENSE.txt file. 5 | // 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | 12 | // Like Imf::StdIFStream, but uses a generic std::std::istream 13 | class StdIStream : public Imf::IStream 14 | { 15 | public: 16 | StdIStream(std::istream &stream, const char fileName[]) : Imf::IStream{fileName}, _is{stream} {} 17 | 18 | bool read(char c[/*n*/], int n) override 19 | { 20 | if (!_is) 21 | throw IEX_NAMESPACE::InputExc("Unexpected end of file."); 22 | 23 | clearError(); 24 | _is.read(c, n); 25 | return checkError(_is, n); 26 | } 27 | 28 | uint64_t tellg() override { return std::streamoff(_is.tellg()); } 29 | 30 | void seekg(uint64_t pos) override 31 | { 32 | _is.seekg(pos); 33 | checkError(_is); 34 | } 35 | 36 | void clear() override { _is.clear(); } 37 | 38 | private: 39 | static void clearError() { errno = 0; } 40 | 41 | static bool checkError(std::istream &is, std::streamsize expected = 0) 42 | { 43 | if (!is) 44 | { 45 | if (errno) 46 | { 47 | IEX_NAMESPACE::throwErrnoExc(); 48 | } 49 | 50 | if (is.gcount() < expected) 51 | { 52 | THROW(IEX_NAMESPACE::InputExc, 53 | "Early end of file: read " << is.gcount() << " out of " << expected << " requested bytes."); 54 | } 55 | 56 | return false; 57 | } 58 | 59 | return true; 60 | } 61 | 62 | std::istream &_is; 63 | }; 64 | 65 | // Like Imf::StdOFStream, but uses a generic std::std::ostream 66 | class StdOStream : public Imf::OStream 67 | { 68 | public: 69 | StdOStream(std::ostream &stream, const char fileName[]) : Imf::OStream{fileName}, _os{stream} {} 70 | 71 | void write(const char c[/*n*/], int n) 72 | { 73 | clearError(); 74 | _os.write(c, n); 75 | checkError(_os); 76 | } 77 | 78 | uint64_t tellp() { return std::streamoff(_os.tellp()); } 79 | 80 | void seekp(uint64_t pos) 81 | { 82 | _os.seekp(pos); 83 | checkError(_os); 84 | } 85 | 86 | private: 87 | // The following error-checking functions were copy&pasted from the OpenEXR source code 88 | static void clearError() { errno = 0; } 89 | 90 | static void checkError(std::ostream &os) 91 | { 92 | if (!os) 93 | { 94 | if (errno) 95 | { 96 | IEX_NAMESPACE::throwErrnoExc(); 97 | } 98 | 99 | throw IEX_NAMESPACE::ErrnoExc("File output failed."); 100 | } 101 | } 102 | 103 | std::ostream &_os; 104 | }; 105 | -------------------------------------------------------------------------------- /src/imageio/heif.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Wojciech Jarosz. All rights reserved. 3 | // Use of this source code is governed by a BSD-style license that can 4 | // be found in the LICENSE.txt file. 5 | // 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "fwd.h" 14 | 15 | // should not throw 16 | bool is_heif_image(std::istream &is) noexcept; 17 | // throws on error 18 | std::vector load_heif_image(std::istream &is, const std::string_view filename); -------------------------------------------------------------------------------- /src/imageio/icc.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Wojciech Jarosz. All rights reserved. 3 | // Use of this source code is governed by a BSD-style license that can 4 | // be found in the LICENSE.txt file. 5 | // 6 | #pragma once 7 | 8 | #include "fwd.h" 9 | #include 10 | 11 | namespace icc 12 | { 13 | 14 | /*! 15 | \brief Linearize a (potentially interleaved) array of floating-point pixel values using the transfer function of the 16 | provided ICC profile. 17 | 18 | This function tries to apply only the inverse transfer function of the ICC profile to the pixel values. It does not 19 | perform color transformations. 20 | 21 | \param[inout] pixels 22 | The pixel values to linearize in place. 23 | \param[in] size 24 | The dimensions of the pixels array in width, height, and number of channels. If size.z > 1 the pixel array is 25 | interleaved. 26 | \param[in] icc_profile 27 | A byte array containing the ICC profile to transform by. 28 | \param[out] tf_description 29 | A description of the transfer function used to linearize the pixel values will be written to this string. 30 | \param[out] red, green, blue, white 31 | If not nullptr, the chromaticities of the ICC profile will be written to these variables. 32 | \returns 33 | True if the pixel values were successfully linearized. 34 | */ 35 | bool linearize_colors(float *pixels, int3 size, const std::vector &icc_profile, 36 | std::string *tf_description = nullptr, float2 *red = nullptr, float2 *green = nullptr, 37 | float2 *blue = nullptr, float2 *white = nullptr); 38 | } // namespace icc 39 | -------------------------------------------------------------------------------- /src/imageio/jxl.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Wojciech Jarosz. All rights reserved. 3 | // Use of this source code is governed by a BSD-style license that can 4 | // be found in the LICENSE.txt file. 5 | // 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "fwd.h" 14 | 15 | // should not throw 16 | bool is_jxl_image(std::istream &is) noexcept; 17 | // throws on error 18 | std::vector load_jxl_image(std::istream &is, const std::string &filename); -------------------------------------------------------------------------------- /src/imageio/pfm.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Wojciech Jarosz. All rights reserved. 3 | // Use of this source code is governed by a BSD-style license that can 4 | // be found in the LICENSE.txt file. 5 | // 6 | 7 | #include "pfm.h" 8 | #include "image.h" 9 | #include "texture.h" 10 | #include "timer.h" 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | using namespace std; 21 | 22 | namespace 23 | { 24 | 25 | float reinterpret_as_host_endian(float f, bool big_endian) 26 | { 27 | static_assert(sizeof(float) == sizeof(unsigned int), "Sizes must match"); 28 | 29 | const auto *byte = (const unsigned char *)&f; 30 | uint32_t i; 31 | if (big_endian) 32 | i = (byte[3] << 0) | (byte[2] << 8) | (byte[1] << 16) | (byte[0] << 24); 33 | else 34 | i = (byte[0] << 0) | (byte[1] << 8) | (byte[2] << 16) | (byte[3] << 24); 35 | 36 | float ret; 37 | memcpy(&ret, &i, sizeof(float)); 38 | return ret; 39 | } 40 | 41 | } // end namespace 42 | 43 | bool is_pfm_image(istream &is) noexcept 44 | { 45 | if (!is.good()) 46 | return false; 47 | 48 | auto start = is.tellg(); 49 | bool ret = false; 50 | 51 | try 52 | { 53 | string magic; 54 | int width, height; 55 | float scale; 56 | 57 | is >> magic >> width >> height >> scale; 58 | 59 | ret = is.good() && (magic == "Pf" || magic == "PF" || magic == "PF4") && width > 0 && height > 0 && 60 | isfinite(scale) && scale != 0; 61 | } 62 | catch (...) 63 | { 64 | } 65 | 66 | // rewind 67 | is.clear(); 68 | is.seekg(start); 69 | return ret; 70 | } 71 | 72 | unique_ptr load_pfm_image(istream &is, const string &filename, int *width, int *height, int *num_channels) 73 | { 74 | try 75 | { 76 | Timer timer; 77 | string magic; 78 | float scale; 79 | 80 | is >> magic >> *width >> *height >> scale; 81 | 82 | if (magic == "Pf") 83 | *num_channels = 1; 84 | else if (magic == "PF") 85 | *num_channels = 3; 86 | else if (magic == "PF4") 87 | *num_channels = 4; 88 | else 89 | throw invalid_argument( 90 | fmt::format("load_pfm_image: Could not deduce number of channels from PFM magic string {}", magic)); 91 | 92 | if (*width <= 0 || *height <= 0) 93 | throw invalid_argument( 94 | fmt::format("load_pfm_image: Invalid image width ({}) or height ({})", *width, *height)); 95 | 96 | if (!isfinite(scale) || scale == 0) 97 | throw invalid_argument(fmt::format("load_pfm_image: Invalid PFM scale {}", scale)); 98 | 99 | bool big_endian = scale > 0.f; 100 | scale = fabsf(scale); 101 | 102 | size_t num_floats = static_cast((*width) * (*height) * (*num_channels)); 103 | auto num_bytes = num_floats * sizeof(float); 104 | std::unique_ptr data(new float[num_floats]); 105 | 106 | // skip last newline at the end of the header. 107 | char c; 108 | while (is.get(c) && c != '\r' && c != '\n'); 109 | 110 | // Read the rest of the file 111 | is.read(reinterpret_cast(data.get()), num_bytes); 112 | if (is.gcount() < (streamsize)num_bytes) 113 | throw invalid_argument{ 114 | fmt::format("load_pfm_image: Expected {} bytes, but could only read {} bytes", is.gcount(), num_bytes)}; 115 | 116 | // multiply data by scale factor 117 | for (size_t i = 0; i < num_floats; ++i) data[i] = scale * reinterpret_as_host_endian(data[i], big_endian); 118 | 119 | spdlog::debug("Reading PFM image '{}' took: {} seconds.", filename, (timer.elapsed() / 1000.f)); 120 | 121 | return data; 122 | } 123 | catch (const exception &e) 124 | { 125 | throw invalid_argument{fmt::format("{} in file '{}'", e.what(), filename)}; 126 | } 127 | } 128 | 129 | vector load_pfm_image(std::istream &is, const string &filename) 130 | { 131 | int3 size; 132 | auto float_data = load_pfm_image(is, filename, &size.x, &size.y, &size.z); 133 | auto image = make_shared(size.xy(), size.z); 134 | image->filename = filename; 135 | image->metadata["bit depth"] = fmt::format("{}-bit (32 bpc)", size.z * 32); 136 | image->metadata["transfer function"] = linear_tf; 137 | 138 | Timer timer; 139 | for (int c = 0; c < size.z; ++c) 140 | image->channels[c].copy_from_interleaved(float_data.get(), size.x, size.y, size.z, c, 141 | [](float v) { return v; }); 142 | spdlog::debug("Copying image data for took: {} seconds.", (timer.elapsed() / 1000.f)); 143 | return {image}; 144 | } 145 | 146 | void write_pfm_image(ostream &os, const string &filename, int width, int height, int num_channels, const float data[]) 147 | { 148 | if (!os) 149 | throw invalid_argument("write_pfm_image: Error opening file '" + filename); 150 | 151 | string magic; 152 | 153 | if (num_channels == 1) 154 | magic = "Pf"; 155 | else if (num_channels == 3) 156 | magic = "PF"; 157 | else if (num_channels == 4) 158 | magic = "PF4"; 159 | else 160 | throw invalid_argument(fmt::format("write_pfm_image: Unsupported number of channels {} when writing file " 161 | "\"{}\". PFM format only supports 1, 3, or 4 channels.", 162 | num_channels, filename)); 163 | os << magic << "\n"; 164 | os << width << " " << height << "\n"; 165 | 166 | // determine system endianness 167 | bool little_endian = false; 168 | { 169 | constexpr int n = 1; 170 | // little endian if true 171 | if (*(char *)&n == 1) 172 | little_endian = true; 173 | } 174 | 175 | os << (little_endian ? "-1.0000000\n" : "1.0000000\n"); 176 | 177 | os.write((const char *)data, width * height * sizeof(float) * num_channels); 178 | } 179 | 180 | void save_pfm_image(const Image &img, ostream &os, const string &filename, float gain) 181 | { 182 | Timer timer; 183 | // get interleaved LDR pixel data 184 | int w = 0, h = 0, n = 0; 185 | auto pixels = img.as_interleaved_floats(&w, &h, &n, gain); 186 | write_pfm_image(os, filename, w, h, n, pixels.get()); 187 | spdlog::info("Saved PFM image to \"{}\" in {} seconds.", filename, (timer.elapsed() / 1000.f)); 188 | } 189 | -------------------------------------------------------------------------------- /src/imageio/pfm.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Wojciech Jarosz. All rights reserved. 3 | // Use of this source code is governed by a BSD-style license that can 4 | // be found in the LICENSE.txt file. 5 | // 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "fwd.h" 14 | 15 | // should not throw 16 | bool is_pfm_image(std::istream &is) noexcept; 17 | 18 | std::vector load_pfm_image(std::istream &is, const std::string &filename); 19 | std::unique_ptr load_pfm_image(std::istream &is, const std::string &filename, int *width, int *height, 20 | int *num_channels); 21 | // throws on error 22 | void write_pfm_image(std::ostream &os, const std::string &filename, int width, int height, int num_channels, 23 | const float data[]); 24 | void save_pfm_image(const Image &img, std::ostream &os, const std::string &filename, float gain = 1.f); -------------------------------------------------------------------------------- /src/imageio/qoi.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Wojciech Jarosz. All rights reserved. 3 | // Use of this source code is governed by a BSD-style license that can 4 | // be found in the LICENSE.txt file. 5 | // 6 | 7 | #define QOI_NO_STDIO 8 | #define QOI_IMPLEMENTATION 9 | #include 10 | 11 | #include "colorspace.h" 12 | #include "image.h" 13 | #include "texture.h" 14 | #include "timer.h" 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | #include "dithermatrix256.h" 27 | 28 | using namespace std; 29 | 30 | bool is_qoi_image(istream &is) noexcept 31 | { 32 | bool ret = false; 33 | try 34 | { 35 | char magic[4]; 36 | is.read(magic, sizeof(magic)); 37 | ret = !!is && is.gcount() == sizeof(magic) && string(magic, sizeof(magic)) == "qoif"; 38 | } 39 | catch (...) 40 | { 41 | // 42 | } 43 | 44 | is.clear(); 45 | is.seekg(0); 46 | return ret; 47 | } 48 | 49 | vector load_qoi_image(istream &is, const string &filename) 50 | { 51 | if (!is_qoi_image(is)) 52 | throw invalid_argument{"QOI: invalid magic string"}; 53 | 54 | // calculate size of stream 55 | is.clear(); 56 | is.seekg(0, is.end); 57 | size_t raw_size = is.tellg(); 58 | is.seekg(0, is.beg); 59 | 60 | // read in the whole stream 61 | vector raw_data(raw_size); 62 | is.read(raw_data.data(), raw_size); 63 | if ((size_t)is.gcount() != raw_size) 64 | throw invalid_argument{ 65 | fmt::format("Failed to read : {} bytes, read : {} bytes", raw_size, (size_t)is.gcount())}; 66 | 67 | qoi_desc desc; 68 | std::unique_ptr decoded_data{ 69 | qoi_decode(raw_data.data(), static_cast(raw_size), &desc, 0), std::free}; 70 | if (!decoded_data.get()) 71 | throw invalid_argument{"Failed to decode data from the QOI format."}; 72 | 73 | int3 size{static_cast(desc.width), static_cast(desc.height), static_cast(desc.channels)}; 74 | if (product(size) == 0) 75 | throw invalid_argument{"Image has zero pixels."}; 76 | 77 | auto image = make_shared(size.xy(), size.z); 78 | image->filename = filename; 79 | image->file_has_straight_alpha = size.z > 3; 80 | image->metadata["loader"] = "qoi"; 81 | image->metadata["bit depth"] = fmt::format("{}-bit (8 bpc)", size.z * 8); 82 | image->metadata["transfer function"] = desc.colorspace == QOI_LINEAR ? linear_tf : srgb_tf; 83 | 84 | bool linearize = desc.colorspace != QOI_LINEAR; 85 | 86 | if (linearize) 87 | spdlog::info("QOI image is sRGB encoded, linearizing."); 88 | 89 | Timer timer; 90 | for (int c = 0; c < size.z; ++c) 91 | image->channels[c].copy_from_interleaved(reinterpret_cast(decoded_data.get()), size.x, size.y, 92 | size.z, c, [linearize, c](uint8_t v) 93 | { return byte_to_f32(v, linearize && c != 3); }); 94 | 95 | spdlog::debug("Copying image channels took: {} seconds.", (timer.elapsed() / 1000.f)); 96 | 97 | return {image}; 98 | } 99 | 100 | void save_qoi_image(const Image &img, ostream &os, const string &filename, float gain, float gamma, bool sRGB, 101 | bool dither) 102 | { 103 | Timer timer; 104 | // get interleaved LDR pixel data 105 | int w = 0, h = 0, n = 0; 106 | auto pixels = img.as_interleaved_bytes(&w, &h, &n, gain, gamma, sRGB, dither); 107 | 108 | // The QOI image format only supports RGB or RGBA data. 109 | if (n != 4 && n != 3) 110 | throw invalid_argument{ 111 | fmt::format("Invalid number of channels {}. QOI format expects either 3 or 4 channels.", n)}; 112 | 113 | // write the data 114 | const qoi_desc desc{ 115 | static_cast(w), // width 116 | static_cast(h), // height 117 | static_cast(n), // number of channels 118 | static_cast(sRGB ? QOI_SRGB : QOI_LINEAR), // colorspace 119 | }; 120 | int encoded_size = 0; 121 | 122 | spdlog::info("Saving {}-channel, {}x{} pixels {} QOI image.", n, w, h, sRGB ? srgb_tf : linear_tf); 123 | std::unique_ptr encoded_data{qoi_encode(pixels.get(), &desc, &encoded_size), 124 | std::free}; 125 | 126 | if (!encoded_data.get()) 127 | throw invalid_argument{"Failed to encode data into the QOI format."}; 128 | 129 | os.write(reinterpret_cast(encoded_data.get()), encoded_size); 130 | spdlog::info("Saved QOI image to \"{}\" in {} seconds.", filename, (timer.elapsed() / 1000.f)); 131 | } 132 | -------------------------------------------------------------------------------- /src/imageio/qoi.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Wojciech Jarosz. All rights reserved. 3 | // Use of this source code is governed by a BSD-style license that can 4 | // be found in the LICENSE.txt file. 5 | // 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "fwd.h" 14 | 15 | // should not throw 16 | bool is_qoi_image(std::istream &is) noexcept; 17 | // throws on error 18 | std::vector load_qoi_image(std::istream &is, const std::string &filename); 19 | /// throws on error 20 | void save_qoi_image(const Image &img, std::ostream &os, const std::string &filename, float gain = 1.f, 21 | float gamma = 2.2f, bool sRGB = true, bool dither = true); -------------------------------------------------------------------------------- /src/imageio/stb.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Wojciech Jarosz. All rights reserved. 3 | // Use of this source code is governed by a BSD-style license that can 4 | // be found in the LICENSE.txt file. 5 | // 6 | 7 | #include "stb.h" 8 | #include "dithermatrix256.h" 9 | #include "image.h" 10 | #include "texture.h" 11 | #include "timer.h" 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | // these pragmas ignore warnings about unused static functions 22 | #if defined(__clang__) 23 | #pragma clang diagnostic push 24 | #pragma clang diagnostic ignored "-Wunused-function" 25 | #pragma clang diagnostic ignored "-Wmissing-field-initializers" 26 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 27 | #elif defined(__GNUC__) || defined(__GNUG__) 28 | #pragma GCC diagnostic push 29 | #pragma GCC diagnostic ignored "-Wunused-function" 30 | #elif defined(_MSC_VER) 31 | #pragma warning(push, 0) 32 | #endif 33 | 34 | // since other libraries might include old versions of stb headers, we declare stb static here 35 | #define STB_IMAGE_STATIC 36 | #define STB_IMAGE_IMPLEMENTATION 37 | #include "stb_image.h" 38 | #undef STB_IMAGE_IMPLEMENTATION 39 | #undef STB_IMAGE_STATIC 40 | 41 | #define STB_IMAGE_WRITE_STATIC 42 | #define STB_IMAGE_WRITE_IMPLEMENTATION 43 | #include "stb_image_write.h" 44 | #undef STB_IMAGE_WRITE_IMPLEMENTATION 45 | #undef STB_IMAGE_WRITE_STATIC 46 | 47 | #if defined(__clang__) 48 | #pragma clang diagnostic pop 49 | #elif defined(__GNUC__) || defined(__GNUG__) 50 | #pragma GCC diagnostic pop 51 | #elif defined(_MSC_VER) 52 | #pragma warning(pop) 53 | #endif 54 | 55 | using namespace std; 56 | 57 | static const stbi_io_callbacks stbi_callbacks = { 58 | // read 59 | [](void *user, char *data, int size) 60 | { 61 | auto stream = reinterpret_cast(user); 62 | stream->read(data, size); 63 | return (int)stream->gcount(); 64 | }, 65 | // seek 66 | [](void *user, int size) { reinterpret_cast(user)->seekg(size, ios_base::cur); }, 67 | // eof 68 | [](void *user) { return (int)reinterpret_cast(user)->eof(); }, 69 | }; 70 | 71 | static bool supported_format(istream &is, json &j) noexcept 72 | { 73 | is.clear(); 74 | is.seekg(0); 75 | try 76 | { 77 | stbi__context s; 78 | stbi__start_callbacks(&s, (stbi_io_callbacks *)&stbi_callbacks, &is); 79 | 80 | // these are ordered like stbi__load_main does for speed an reliability 81 | if (stbi__png_test(&s)) 82 | j["format"] = "png"; 83 | else if (stbi__bmp_test(&s)) 84 | j["format"] = "bmp"; 85 | else if (stbi__gif_test(&s)) 86 | j["format"] = "gif"; 87 | else if (stbi__psd_test(&s)) 88 | j["format"] = "psd"; 89 | else if (stbi__pic_test(&s)) 90 | j["format"] = "pic"; 91 | else if (stbi__jpeg_test(&s)) 92 | j["format"] = "jpeg"; 93 | else if (stbi__pnm_test(&s)) 94 | j["format"] = "pnm"; 95 | else if (stbi__hdr_test(&s)) 96 | j["format"] = "hdr"; 97 | else if (stbi__tga_test(&s)) 98 | j["format"] = "tga"; 99 | } 100 | catch (...) 101 | { 102 | // 103 | } 104 | // shouldn't be necessary, but just in case: 105 | // rewind 106 | is.clear(); 107 | is.seekg(0); 108 | return j.contains("format"); 109 | } 110 | 111 | bool is_stb_image(istream &is) noexcept 112 | { 113 | json j; 114 | return supported_format(is, j); 115 | } 116 | 117 | vector load_stb_image(istream &is, const string &filename) 118 | { 119 | // stbi doesn't do proper srgb, but uses gamma=2.2 instead, so override it. 120 | // we'll do our own srgb correction 121 | stbi_ldr_to_hdr_scale(1.0f); 122 | stbi_ldr_to_hdr_gamma(1.0f); 123 | 124 | int3 size; 125 | using FloatBuffer = std::unique_ptr; 126 | auto float_data = 127 | FloatBuffer{stbi_loadf_from_callbacks(&stbi_callbacks, &is, &size.x, &size.y, &size.z, 0), stbi_image_free}; 128 | if (!float_data) 129 | throw invalid_argument{fmt::format("STB1: {}", stbi_failure_reason())}; 130 | 131 | if (product(size) == 0) 132 | throw invalid_argument{"STB: Image has zero pixels."}; 133 | 134 | auto image = make_shared(size.xy(), size.z); 135 | image->filename = filename; 136 | image->file_has_straight_alpha = true; 137 | json j; 138 | if (supported_format(is, j)) 139 | image->metadata["loader"] = fmt::format("stb_image ({})", j["format"].get()); 140 | else 141 | throw runtime_error{ 142 | "STB: loaded the image, but then couldn't figure out the format (this should never happen)."}; 143 | bool linearize = j["format"] != "hdr"; 144 | if (!linearize) 145 | image->metadata["bit depth"] = "8:8:8:8 rgbe"; 146 | else if (stbi_is_16_bit_from_callbacks(&stbi_callbacks, &is)) 147 | image->metadata["bit depth"] = "16 bpp"; 148 | else 149 | image->metadata["bit depth"] = "8 bpc"; 150 | 151 | image->metadata["transfer function"] = linearize ? srgb_tf : linear_tf; 152 | 153 | if (linearize) 154 | spdlog::info("Assuming STB image is sRGB encoded, linearizing."); 155 | 156 | Timer timer; 157 | for (int c = 0; c < size.z; ++c) 158 | image->channels[c].copy_from_interleaved(float_data.get(), size.x, size.y, size.z, c, [linearize, c](float v) 159 | { return linearize && c != 3 ? SRGBToLinear(v) : v; }); 160 | 161 | spdlog::debug("Copying image channels took: {} seconds.", (timer.elapsed() / 1000.f)); 162 | 163 | return {image}; 164 | } 165 | 166 | void save_stb_image(const Image &img, ostream &os, const string &filename, float gain, float gamma, bool sRGB, 167 | bool dither) 168 | { 169 | Timer timer; 170 | static const auto ostream_write_func = [](void *context, void *data, int size) 171 | { reinterpret_cast(context)->write(reinterpret_cast(data), size); }; 172 | 173 | string extension = to_lower(get_extension(filename)); 174 | 175 | if (extension == "hdr") 176 | { 177 | // get interleaved HDR pixel data 178 | int w = 0, h = 0, n = 0; 179 | auto pixels = img.as_interleaved_floats(&w, &h, &n, gain); 180 | if (stbi_write_hdr_to_func(ostream_write_func, &os, w, h, n, pixels.get()) != 0) 181 | throw runtime_error("Failed to write HDR image via stb."); 182 | spdlog::info("Saved HDR image via stb to '{}' in {} seconds.", filename, (timer.elapsed() / 1000.f)); 183 | } 184 | else 185 | { 186 | // get interleaved LDR pixel data 187 | int w = 0, h = 0, n = 0; 188 | auto pixels = img.as_interleaved_bytes(&w, &h, &n, gain, gamma, sRGB, dither); 189 | 190 | bool ret = false; 191 | if (extension == "png") 192 | ret = stbi_write_png_to_func(ostream_write_func, &os, w, h, n, pixels.get(), 0) != 0; 193 | else if (extension == "bmp") 194 | ret = stbi_write_bmp_to_func(ostream_write_func, &os, w, h, n, pixels.get()) != 0; 195 | else if (extension == "tga") 196 | ret = stbi_write_tga_to_func(ostream_write_func, &os, w, h, n, pixels.get()) != 0; 197 | else if (extension == "jpg" || extension == "jpeg") 198 | ret = stbi_write_jpg_to_func(ostream_write_func, &os, w, h, n, pixels.get(), 100) != 0; 199 | else 200 | throw invalid_argument( 201 | fmt::format("Could not determine desired file type from extension \"{}\".", extension)); 202 | if (ret) 203 | spdlog::info("Saved {} image via stb to '{}' in {} seconds.", to_upper(extension), filename, 204 | (timer.elapsed() / 1000.f)); 205 | else 206 | throw runtime_error(fmt::format("Failed to write {} image via stb.", to_upper(extension))); 207 | } 208 | } 209 | -------------------------------------------------------------------------------- /src/imageio/stb.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Wojciech Jarosz. All rights reserved. 3 | // Use of this source code is governed by a BSD-style license that can 4 | // be found in the LICENSE.txt file. 5 | // 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "fwd.h" 14 | 15 | // should not throw 16 | bool is_stb_image(std::istream &is) noexcept; 17 | // throws on error 18 | std::vector load_stb_image(std::istream &is, const std::string &filename); 19 | // throws on error 20 | void save_stb_image(const Image &img, std::ostream &os, const std::string &filename, float gain = 1.f, 21 | float gamma = 2.2f, bool sRGB = true, bool dither = true); -------------------------------------------------------------------------------- /src/imageio/uhdr.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Wojciech Jarosz. All rights reserved. 3 | // Use of this source code is governed by a BSD-style license that can 4 | // be found in the LICENSE.txt file. 5 | // 6 | 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "fwd.h" 14 | 15 | // should not throw 16 | bool is_uhdr_image(std::istream &is) noexcept; 17 | // throws on error 18 | std::vector load_uhdr_image(std::istream &is, const std::string &filename); 19 | // throws on error 20 | void save_uhdr_image(const Image &img, std::ostream &os, const std::string &filename, float gain = 1.f); -------------------------------------------------------------------------------- /src/json.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Wojciech Jarosz. All rights reserved. 3 | // Use of this source code is governed by a BSD-style license that can 4 | // be found in the LICENSE.txt file. 5 | // 6 | 7 | #pragma once 8 | 9 | #include "linalg.h" 10 | #include "nlohmann/json.hpp" 11 | #include 12 | 13 | using json = nlohmann::json; 14 | 15 | namespace linalg 16 | { 17 | 18 | /// Serialize a Vec3 to json 19 | template 20 | inline void to_json(nlohmann::json &j, const vec &v) 21 | { 22 | j = std::vector(&(v[0]), &(v[0]) + N); 23 | } 24 | 25 | /// parse a Vec from json 26 | template 27 | inline void from_json(const nlohmann::json &j, vec &v) 28 | { 29 | if (j.is_object()) 30 | throw std::invalid_argument( 31 | fmt::format("Can't parse a vec{}. Expecting a json array, but got a json object.", N)); 32 | 33 | size_t size = std::min(j.size(), (size_t)N); 34 | if (size != j.size()) 35 | spdlog::error(fmt::format("Incorrect array size when trying to parse a vec{}. " 36 | "Expecting {} values but found {}. Will only read the first {} elements here:\n{}", 37 | N, N, (int)j.size(), size, j.dump(4))); 38 | 39 | for (size_t i = 0; i < size; ++i) j.at(i).get_to(v[(int)i]); 40 | } 41 | 42 | } // namespace linalg -------------------------------------------------------------------------------- /src/opengl_check.cpp: -------------------------------------------------------------------------------- 1 | #if defined(HELLOIMGUI_HAS_OPENGL) 2 | 3 | #include "hello_imgui/hello_imgui.h" 4 | #include "hello_imgui/hello_imgui_include_opengl.h" // cross-platform way to include OpenGL headers 5 | #include 6 | 7 | bool check_glerror(const char *cmd, const char *file, int line) 8 | { 9 | GLenum err = glGetError(); 10 | const char *msg = nullptr; 11 | 12 | switch (err) 13 | { 14 | case GL_NO_ERROR: return false; 15 | case GL_INVALID_ENUM: msg = "invalid enumeration"; break; 16 | case GL_INVALID_VALUE: msg = "invalid value"; break; 17 | case GL_INVALID_OPERATION: msg = "invalid operation"; break; 18 | case GL_INVALID_FRAMEBUFFER_OPERATION: msg = "invalid framebuffer operation"; break; 19 | case GL_OUT_OF_MEMORY: msg = "out of memory"; break; 20 | #ifndef __EMSCRIPTEN__ 21 | case GL_STACK_UNDERFLOW: msg = "stack underflow"; break; 22 | case GL_STACK_OVERFLOW: msg = "stack overflow"; break; 23 | #endif 24 | default: msg = "unknown error"; break; 25 | } 26 | 27 | spdlog::error("OpenGL error {}:{} ({}) during operation \"{}\"!\n", file, line, msg, cmd); 28 | return true; 29 | } 30 | 31 | #endif // defined(HELLOIMGUI_HAS_OPENGL) 32 | -------------------------------------------------------------------------------- /src/opengl_check.h: -------------------------------------------------------------------------------- 1 | /** 2 | \file opengl_check.h 3 | */ 4 | #pragma once 5 | 6 | bool check_glerror(const char *cmd, const char *file, int line); 7 | 8 | #if defined(NDEBUG) 9 | #define CHK(cmd) cmd 10 | #else 11 | #define CHK(cmd) \ 12 | do \ 13 | { \ 14 | cmd; \ 15 | while (check_glerror(#cmd, __FILE__, __LINE__)) \ 16 | { \ 17 | } \ 18 | } while (false) 19 | #endif -------------------------------------------------------------------------------- /src/progress.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Wojciech Jarosz. All rights reserved. 3 | // Use of this source code is governed by a BSD-style license that can 4 | // be found in the LICENSE.txt file. 5 | // 6 | 7 | #include "progress.h" 8 | #include 9 | 10 | AtomicProgress::AtomicProgress(bool create_state, float total_percentage) : 11 | m_num_steps(1), m_percentage_of_parent(total_percentage), 12 | m_step_percent(m_num_steps == 0 ? total_percentage : total_percentage / m_num_steps), 13 | m_state(create_state ? std::make_shared() : nullptr) 14 | { 15 | } 16 | 17 | AtomicProgress::AtomicProgress(float percentage_of_parent, const AtomicProgress &parent) : 18 | m_num_steps(1), m_percentage_of_parent(parent.m_percentage_of_parent * percentage_of_parent), 19 | m_step_percent(m_num_steps == 0 ? m_percentage_of_parent : m_percentage_of_parent / m_num_steps), 20 | m_state(parent.m_state) 21 | { 22 | } 23 | 24 | void AtomicProgress::reset_progress(float p) 25 | { 26 | if (!m_state) 27 | return; 28 | 29 | m_state->progress = p; 30 | } 31 | 32 | float AtomicProgress::progress() const { return m_state ? float(m_state->progress) : -1.f; } 33 | 34 | void AtomicProgress::set_available_percent(float available_percent) 35 | { 36 | m_percentage_of_parent = available_percent; 37 | m_step_percent = m_num_steps == 0 ? available_percent : available_percent / m_num_steps; 38 | } 39 | 40 | void AtomicProgress::set_num_steps(int num_steps) 41 | { 42 | m_num_steps = num_steps; 43 | m_step_percent = m_num_steps == 0 ? m_percentage_of_parent : m_percentage_of_parent / m_num_steps; 44 | } 45 | 46 | AtomicProgress &AtomicProgress::operator+=(int steps) 47 | { 48 | if (!m_state) 49 | return *this; 50 | 51 | m_state->progress += steps * m_step_percent; 52 | 53 | return *this; 54 | } 55 | 56 | bool AtomicProgress::canceled() const { return m_state ? m_state->canceled : false; } 57 | 58 | void AtomicProgress::cancel() 59 | { 60 | if (!m_state) 61 | return; 62 | 63 | m_state->canceled = true; 64 | } -------------------------------------------------------------------------------- /src/progress.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Wojciech Jarosz. All rights reserved. 3 | // Use of this source code is governed by a BSD-style license that can 4 | // be found in the LICENSE.txt file. 5 | // 6 | #pragma once 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | /*! 14 | * A fixed-point fractional number stored using an std::atomic 15 | */ 16 | template 17 | class AtomicFixed 18 | { 19 | public: 20 | static const Fixed ScalingFactor = (1 << FractionBits); 21 | 22 | static Fixed float2fixed(float b) { return (Fixed)std::round(b * ScalingFactor); } 23 | 24 | static float fixed2float(Fixed f) { return float(f) / ScalingFactor; } 25 | 26 | std::atomic f; 27 | 28 | AtomicFixed() = default; 29 | 30 | explicit AtomicFixed(float d) : f(float2fixed(d)) 31 | { 32 | // empty 33 | } 34 | 35 | explicit operator float() const { return fixed2float(f); } 36 | 37 | Fixed operator=(float b) { return (f = float2fixed(b)); } 38 | 39 | Fixed operator+=(float b) { return (f += float2fixed(b)); } 40 | 41 | Fixed operator-=(float b) { return (f -= float2fixed(b)); } 42 | 43 | // Disabling to avoid accidental non-atomic operations 44 | // /// This operator is *NOT* atomic 45 | // Fixed operator*=(float b) 46 | // { 47 | // return (f = Fixed(BigFixed(f) * BigFixed(float2fixed(b))) / ScalingFactor); 48 | // } 49 | 50 | // /// This operator is *NOT* atomic 51 | // Fixed operator/=(float b) 52 | // { 53 | // return (f = Fixed((BigFixed(f) * ScalingFactor) / float2fixed(b))); 54 | // } 55 | 56 | bool operator<(float b) const { return f < float2fixed(b); } 57 | 58 | bool operator<=(float b) const { return f <= float2fixed(b); } 59 | 60 | bool operator>(float b) const { return f > float2fixed(b); } 61 | 62 | bool operator>=(float b) const { return f >= float2fixed(b); } 63 | 64 | bool operator==(float b) const { return f == float2fixed(b); } 65 | 66 | bool operator!=(float b) const { return f != float2fixed(b); } 67 | }; 68 | 69 | using AtomicFixed16 = AtomicFixed; 70 | using AtomicFixed32 = AtomicFixed; 71 | 72 | /** 73 | Helper object to manage the progress display. 74 | 75 | \code{.cpp} 76 | { 77 | AtomicProgress p1(true); 78 | p1.set_num_steps(10); 79 | for (int i = 0; i < 10; ++i, ++p1) 80 | { 81 | // do something 82 | } 83 | } // end progress p1 84 | \endcode 85 | */ 86 | class AtomicProgress 87 | { 88 | public: 89 | using AtomicPercent32 = AtomicFixed; 90 | 91 | explicit AtomicProgress(bool create_state = false, float total_percentage = 1.f); 92 | AtomicProgress(float percentage_of_parent, const AtomicProgress &parent); 93 | 94 | // access to the atomic internal storage 95 | void reset_progress(float p = 0.f); 96 | float progress() const; 97 | void set_done() { reset_progress(1.f); } 98 | void set_busy() { reset_progress(-1.f); } 99 | bool canceled() const; 100 | void cancel(); 101 | 102 | bool has_state() const { return (bool)m_state; } 103 | 104 | // access to the discrete stepping 105 | void set_available_percent(float percent); 106 | void set_num_steps(int num_steps); 107 | AtomicProgress &operator+=(int steps); 108 | AtomicProgress &operator++() { return ((*this) += 1); } 109 | 110 | private: 111 | int m_num_steps; 112 | float m_percentage_of_parent, m_step_percent; 113 | struct State 114 | { 115 | State() : progress(0.f), canceled(false) {} 116 | 117 | AtomicPercent32 progress; ///< Atomic internal state of progress 118 | 119 | // theoretically this should be guarded by an std::atomic, 120 | // but on any reasonable architecture a bool will be atomic 121 | bool canceled; ///< Flag set if the calling code wants to cancel the associated task 122 | }; 123 | 124 | std::shared_ptr m_state; 125 | }; -------------------------------------------------------------------------------- /src/renderpass.h: -------------------------------------------------------------------------------- 1 | /** 2 | \file renderpass.h 3 | */ 4 | #pragma once 5 | 6 | #include "fwd.h" 7 | #include 8 | 9 | class Shader; 10 | 11 | /** 12 | An abstraction for rendering passes that work with OpenGL, OpenGL ES, and Metal. 13 | 14 | This is a greatly simplified version of NanoGUI's RenderPass class. Original copyright follows. 15 | ---------- 16 | NanoGUI was developed by Wenzel Jakob . 17 | The widget drawing code is based on the NanoVG demo application 18 | by Mikko Mononen. 19 | 20 | All rights reserved. Use of this source code is governed by a 21 | BSD-style license that can be found in the LICENSE.txt file. 22 | */ 23 | class RenderPass 24 | { 25 | public: 26 | /// Depth test 27 | enum class DepthTest 28 | { 29 | Never, 30 | Less, 31 | Equal, 32 | LessEqual, 33 | Greater, 34 | NotEqual, 35 | GreaterEqual, 36 | Always 37 | }; 38 | 39 | /// Culling mode 40 | enum class CullMode 41 | { 42 | Disabled, 43 | Front, 44 | Back 45 | }; 46 | 47 | /** 48 | * Create a new render pass for rendering to the main color and (optionally) depth buffer. 49 | * 50 | * \param write_depth 51 | * Should we write to the depth buffer? 52 | * 53 | * \param clear 54 | * Should \ref enter() begin by clearing all buffers? 55 | */ 56 | RenderPass(bool write_depth = true, bool clear = true); 57 | 58 | ~RenderPass(); 59 | 60 | /** 61 | * Begin the render pass 62 | * 63 | * The specified drawing state (e.g. depth tests, culling mode, blending mode) are automatically set up at this 64 | * point. Later changes between \ref begin() and \ref end() are possible but cause additional OpenGL/GLES/Metal API 65 | * calls. 66 | */ 67 | void begin(); 68 | 69 | /// Finish the render pass 70 | void end(); 71 | 72 | /// Return the clear color for a given color attachment 73 | const float4 &clear_color() const { return m_clear_color; } 74 | 75 | /// Set the clear color for a given color attachment 76 | void set_clear_color(const float4 &color); 77 | 78 | /// Return the clear depth for the depth attachment 79 | float clear_depth() const { return m_clear_depth; } 80 | 81 | /// Set the clear depth for the depth attachment 82 | void set_clear_depth(float depth); 83 | 84 | /// Specify the depth test and depth write mask of this render pass 85 | void set_depth_test(DepthTest depth_test, bool depth_write); 86 | 87 | /// Return the depth test and depth write mask of this render pass 88 | std::pair depth_test() const { return {m_depth_test, m_depth_write}; } 89 | 90 | /// Set the pixel offset and size of the viewport region 91 | void set_viewport(const int2 &offset, const int2 &size); 92 | 93 | /// Return the pixel offset and size of the viewport region 94 | std::pair viewport() { return {m_viewport_offset, m_viewport_size}; } 95 | 96 | /// Specify the culling mode associated with the render pass 97 | void set_cull_mode(CullMode mode); 98 | 99 | /// Return the culling mode associated with the render pass 100 | CullMode cull_mode() const { return m_cull_mode; } 101 | 102 | /// Resize all texture targets attached to the render pass 103 | void resize(const int2 &size); 104 | 105 | #if defined(HELLOIMGUI_HAS_METAL) 106 | void *command_encoder() const { return m_command_encoder; } 107 | void *command_buffer() const { return m_command_buffer; } 108 | #endif 109 | 110 | protected: 111 | bool m_clear; 112 | float4 m_clear_color = float4{0, 0, 0, 0}; 113 | float m_clear_depth = 1.f; 114 | int2 m_viewport_offset = int2{0}; 115 | int2 m_viewport_size = int2{0}; 116 | int2 m_framebuffer_size = int2{0}; 117 | DepthTest m_depth_test; 118 | bool m_depth_write; 119 | CullMode m_cull_mode; 120 | bool m_active = false; 121 | 122 | #if defined(HELLOIMGUI_HAS_OPENGL) 123 | int4 m_viewport_backup, m_scissor_backup; 124 | bool m_depth_test_backup; 125 | bool m_depth_write_backup; 126 | bool m_scissor_test_backup; 127 | bool m_cull_face_backup; 128 | bool m_blend_backup; 129 | #elif defined(HELLOIMGUI_HAS_METAL) 130 | void *m_command_buffer; 131 | void *m_command_encoder; 132 | void *m_pass_descriptor; 133 | std::unique_ptr m_clear_shader; 134 | #endif 135 | }; 136 | -------------------------------------------------------------------------------- /src/renderpass_gl.cpp: -------------------------------------------------------------------------------- 1 | #if defined(HELLOIMGUI_HAS_OPENGL) 2 | 3 | #include "hello_imgui/hello_imgui_include_opengl.h" // cross-platform way to include OpenGL headers 4 | #include "opengl_check.h" 5 | #include "renderpass.h" 6 | 7 | #include 8 | #include 9 | 10 | RenderPass::RenderPass(bool write_depth, bool clear) : 11 | m_clear(clear), m_depth_test(write_depth ? DepthTest::Less : DepthTest::Always), m_depth_write(write_depth), 12 | m_cull_mode(CullMode::Back) 13 | { 14 | } 15 | 16 | RenderPass::~RenderPass() {} 17 | 18 | void RenderPass::begin() 19 | { 20 | #if !defined(NDEBUG) 21 | if (m_active) 22 | throw std::runtime_error("RenderPass::begin(): render pass is already active!"); 23 | #endif 24 | m_active = true; 25 | 26 | CHK(glGetIntegerv(GL_VIEWPORT, &m_viewport_backup[0])); 27 | CHK(glGetIntegerv(GL_SCISSOR_BOX, &m_scissor_backup[0])); 28 | GLboolean depth_write; 29 | CHK(glGetBooleanv(GL_DEPTH_WRITEMASK, &depth_write)); 30 | m_depth_write_backup = depth_write; 31 | 32 | m_depth_test_backup = glIsEnabled(GL_DEPTH_TEST); 33 | m_scissor_test_backup = glIsEnabled(GL_SCISSOR_TEST); 34 | m_cull_face_backup = glIsEnabled(GL_CULL_FACE); 35 | m_blend_backup = glIsEnabled(GL_BLEND); 36 | 37 | set_viewport(m_viewport_offset, m_viewport_size); 38 | 39 | if (m_clear) 40 | { 41 | GLenum what = 0; 42 | if (m_depth_write) 43 | { 44 | CHK(glClearDepthf(m_clear_depth)); 45 | what |= GL_DEPTH_BUFFER_BIT; 46 | } 47 | 48 | CHK(glClearColor(m_clear_color.x, m_clear_color.y, m_clear_color.z, m_clear_color.w)); 49 | what |= GL_COLOR_BUFFER_BIT; 50 | 51 | CHK(glClear(what)); 52 | } 53 | 54 | set_depth_test(m_depth_test, m_depth_write); 55 | set_cull_mode(m_cull_mode); 56 | 57 | if (m_blend_backup) 58 | CHK(glDisable(GL_BLEND)); 59 | } 60 | 61 | void RenderPass::end() 62 | { 63 | #if !defined(NDEBUG) 64 | if (!m_active) 65 | throw std::runtime_error("RenderPass::end(): render pass is not active!"); 66 | #endif 67 | 68 | CHK(glViewport(m_viewport_backup[0], m_viewport_backup[1], m_viewport_backup[2], m_viewport_backup[3])); 69 | CHK(glScissor(m_scissor_backup[0], m_scissor_backup[1], m_scissor_backup[2], m_scissor_backup[3])); 70 | 71 | if (m_depth_test_backup) 72 | CHK(glEnable(GL_DEPTH_TEST)); 73 | else 74 | CHK(glDisable(GL_DEPTH_TEST)); 75 | 76 | CHK(glDepthMask(m_depth_write_backup)); 77 | 78 | if (m_scissor_test_backup) 79 | CHK(glEnable(GL_SCISSOR_TEST)); 80 | else 81 | CHK(glDisable(GL_SCISSOR_TEST)); 82 | 83 | if (m_cull_face_backup) 84 | CHK(glEnable(GL_CULL_FACE)); 85 | else 86 | CHK(glDisable(GL_CULL_FACE)); 87 | 88 | if (m_blend_backup) 89 | CHK(glEnable(GL_BLEND)); 90 | else 91 | CHK(glDisable(GL_BLEND)); 92 | 93 | m_active = false; 94 | } 95 | 96 | void RenderPass::resize(const int2 &size) 97 | { 98 | m_framebuffer_size = size; 99 | m_viewport_offset = int2(0, 0); 100 | m_viewport_size = size; 101 | } 102 | 103 | void RenderPass::set_clear_color(const float4 &color) { m_clear_color = color; } 104 | 105 | void RenderPass::set_clear_depth(float depth) { m_clear_depth = depth; } 106 | 107 | void RenderPass::set_viewport(const int2 &offset, const int2 &size) 108 | { 109 | m_viewport_offset = offset; 110 | m_viewport_size = size; 111 | 112 | if (m_active) 113 | { 114 | int ypos = m_framebuffer_size.y - m_viewport_size.y - m_viewport_offset.y; 115 | CHK(glViewport(m_viewport_offset.x, ypos, m_viewport_size.x, m_viewport_size.y)); 116 | // fmt::print("RenderPass::viewport({}, {}, {}, {})\n", m_viewport_offset.x, ypos, m_viewport_size.x, 117 | // m_viewport_size.y); 118 | CHK(glScissor(m_viewport_offset.x, ypos, m_viewport_size.x, m_viewport_size.y)); 119 | 120 | if (m_viewport_offset == int2(0, 0) && m_viewport_size == m_framebuffer_size) 121 | CHK(glDisable(GL_SCISSOR_TEST)); 122 | else 123 | CHK(glEnable(GL_SCISSOR_TEST)); 124 | } 125 | } 126 | 127 | void RenderPass::set_depth_test(DepthTest depth_test, bool depth_write) 128 | { 129 | m_depth_test = depth_test; 130 | m_depth_write = depth_write; 131 | 132 | if (m_active) 133 | { 134 | if (depth_test != DepthTest::Always) 135 | { 136 | GLenum func; 137 | switch (depth_test) 138 | { 139 | case DepthTest::Never: func = GL_NEVER; break; 140 | case DepthTest::Less: func = GL_LESS; break; 141 | case DepthTest::Equal: func = GL_EQUAL; break; 142 | case DepthTest::LessEqual: func = GL_LEQUAL; break; 143 | case DepthTest::Greater: func = GL_GREATER; break; 144 | case DepthTest::NotEqual: func = GL_NOTEQUAL; break; 145 | case DepthTest::GreaterEqual: func = GL_GEQUAL; break; 146 | default: throw std::invalid_argument("Shader::set_depth_test(): invalid depth test mode!"); 147 | } 148 | CHK(glEnable(GL_DEPTH_TEST)); 149 | CHK(glDepthFunc(func)); 150 | } 151 | else 152 | { 153 | CHK(glDisable(GL_DEPTH_TEST)); 154 | } 155 | CHK(glDepthMask(depth_write ? GL_TRUE : GL_FALSE)); 156 | // fmt::print("RenderPass::set_depth_test({}, {})\n", (int)depth_test, depth_write); 157 | } 158 | } 159 | 160 | void RenderPass::set_cull_mode(CullMode cull_mode) 161 | { 162 | m_cull_mode = cull_mode; 163 | 164 | if (m_active) 165 | { 166 | if (cull_mode == CullMode::Disabled) 167 | { 168 | CHK(glDisable(GL_CULL_FACE)); 169 | } 170 | else 171 | { 172 | CHK(glEnable(GL_CULL_FACE)); 173 | if (cull_mode == CullMode::Front) 174 | CHK(glCullFace(GL_FRONT)); 175 | else if (cull_mode == CullMode::Back) 176 | CHK(glCullFace(GL_BACK)); 177 | else 178 | throw std::invalid_argument("Shader::set_cull_mode(): invalid cull mode!"); 179 | } 180 | } 181 | } 182 | 183 | #endif // defined(HELLOIMGUI_HAS_OPENGL) 184 | -------------------------------------------------------------------------------- /src/ringbuffer_color_sink.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | namespace spdlog 12 | { 13 | namespace sinks 14 | { 15 | 16 | // Like ringbuffer_sink, but gives access to the color range 17 | template 18 | class ringbuffer_color_sink : public base_sink 19 | { 20 | public: 21 | struct LogItem 22 | { 23 | std::string message; 24 | level::level_enum level; 25 | size_t color_range_start; 26 | size_t color_range_end; 27 | }; 28 | 29 | explicit ringbuffer_color_sink(int max_items = 1024) : max_items_(max_items), q_(max_items) {} 30 | 31 | ~ringbuffer_color_sink() { flush_(); } 32 | 33 | void iterate(const std::function &iterator) 34 | { 35 | std::lock_guard lock(base_sink::mutex_); 36 | for (size_t i = 0; i < q_.size(); ++i) 37 | if (!iterator(q_.at(i))) 38 | break; 39 | } 40 | 41 | void clear_messages() 42 | { 43 | std::lock_guard lock(base_sink::mutex_); 44 | q_ = details::circular_q{max_items_}; 45 | } 46 | 47 | // returns true if there are new logged items since the last time this function was called 48 | bool has_new_items() { return has_new_items_.exchange(false); } 49 | 50 | protected: 51 | void sink_it_(const details::log_msg &msg) override 52 | { 53 | memory_buf_t formatted; 54 | base_sink::formatter_->format(msg, formatted); 55 | 56 | q_.push_back({SPDLOG_BUF_TO_STRING(formatted), msg.level, msg.color_range_start, msg.color_range_end}); 57 | has_new_items_ = true; 58 | } 59 | void flush_() override {} 60 | 61 | private: 62 | size_t max_items_ = 0; 63 | details::circular_q q_; 64 | std::atomic has_new_items_ = false; 65 | }; 66 | 67 | using ringbuffer_color_sink_mt = ringbuffer_color_sink; 68 | using ringbuffer_color_sink_st = ringbuffer_color_sink; 69 | 70 | } // namespace sinks 71 | 72 | // 73 | // Factory functions 74 | // 75 | 76 | template 77 | inline std::shared_ptr dear_logger_mt(const std::string &logger_name, int max_items = 1024) 78 | { 79 | return Factory::template create(logger_name, max_items); 80 | } 81 | 82 | template 83 | inline std::shared_ptr dear_logger_st(const std::string &logger_name, int max_items = 1024) 84 | { 85 | return Factory::template create(logger_name, max_items); 86 | } 87 | 88 | } // namespace spdlog 89 | -------------------------------------------------------------------------------- /src/shader.cpp: -------------------------------------------------------------------------------- 1 | #include "shader.h" 2 | 3 | // #include 4 | #include 5 | #include 6 | 7 | #include "hello_imgui/hello_imgui.h" 8 | 9 | using std::string; 10 | using std::string_view; 11 | 12 | #if defined(HELLOIMGUI_HAS_METAL) 13 | static const string shader_extensions[] = {".metallib", ".metal", ".h"}; 14 | #elif defined(HELLOIMGUI_HAS_OPENGL) 15 | static const string shader_extensions[] = {".glsl", ".vs", ".fs", ".gs", ".vsf", ".fsh", ".gsh", 16 | ".vshader", ".fshader", ".gshader", ".comp", ".vert", ".tesc", ".tese", 17 | ".frag", ".geom", ".glslv", ".glslf", ".glslg"}; 18 | #endif 19 | static const size_t num_extensions = sizeof(shader_extensions) / sizeof(shader_extensions[0]); 20 | 21 | string Shader::from_asset(string_view basename) 22 | { 23 | for (size_t i = 0; i < num_extensions; ++i) 24 | { 25 | string filename = basename.data() + shader_extensions[i]; 26 | 27 | if (!HelloImGui::AssetExists(filename)) 28 | continue; 29 | 30 | string full_path = HelloImGui::assetFileFullPath(filename); 31 | spdlog::info("Loading shader from \"{}\"...", full_path); 32 | auto shader_txt = HelloImGui::LoadAssetFileData(filename.c_str()); 33 | if (shader_txt.data == nullptr) 34 | throw std::runtime_error(fmt::format("Cannot load shader from file \"{}\"", filename)); 35 | 36 | auto source = string((char *)shader_txt.data, shader_txt.dataSize); 37 | HelloImGui::FreeAssetFileData(&shader_txt); 38 | return source; 39 | } 40 | throw std::runtime_error(fmt::format( 41 | "Could not find a shader with base filename \"{}\" with any known shader file extensions.", basename)); 42 | } 43 | 44 | string Shader::prepend_includes(string_view shader_string, const std::vector &include_files) 45 | { 46 | // if the shader_string is actually a precompiled binary, we can't prepend 47 | if (shader_string.size() > 4 && strncmp(shader_string.data(), "MTLB", 4) == 0) 48 | { 49 | spdlog::error("Cannot add #includes to precompiled shaders, skipping."); 50 | return string(shader_string); 51 | } 52 | 53 | std::string includes; 54 | 55 | for (auto &i : include_files) includes += from_asset(i) + "\n"; 56 | 57 | if (includes.empty()) 58 | return string(shader_string); 59 | 60 | std::istringstream iss(shader_string.data()); 61 | std::ostringstream oss; 62 | std::string line; 63 | 64 | // first copy over all the #include or #version lines. these should stay at the top of the shader 65 | while (std::getline(iss, line) && (line.substr(0, 8) == "#include" || line.substr(0, 8) == "#version")) 66 | oss << line << std::endl; 67 | 68 | // now insert the new #includes 69 | oss << includes; 70 | 71 | // and copy over the rest of the lines in the shader 72 | do { 73 | oss << line << std::endl; 74 | } while (std::getline(iss, line)); 75 | 76 | // spdlog::trace("GLSL #includes: {};\n MERGED: {}", includes, oss.str()); 77 | 78 | return oss.str(); 79 | } 80 | 81 | void Shader::set_buffer_divisor(const string &name, size_t divisor) 82 | { 83 | auto it = m_buffers.find(name); 84 | if (it == m_buffers.end()) 85 | throw std::invalid_argument("Shader::set_buffer_divisor(): could not find argument named \"" + name + "\""); 86 | 87 | Buffer &buf = m_buffers[name]; 88 | buf.instance_divisor = divisor; 89 | buf.dirty = true; 90 | } 91 | 92 | void Shader::set_buffer_pointer_offset(const string &name, size_t offset) 93 | { 94 | auto it = m_buffers.find(name); 95 | if (it == m_buffers.end()) 96 | throw std::invalid_argument("Shader::set_buffer_pointer_offset(): could not find argument named \"" + name + 97 | "\""); 98 | 99 | Buffer &buf = m_buffers[name]; 100 | buf.pointer_offset = offset; 101 | buf.dirty = true; 102 | } 103 | 104 | string Shader::Buffer::to_string() const 105 | { 106 | string result = "Buffer[type="; 107 | switch (type) 108 | { 109 | case BufferType::VertexBuffer: result += "vertex"; break; 110 | case BufferType::FragmentBuffer: result += "fragment"; break; 111 | case BufferType::UniformBuffer: result += "uniform"; break; 112 | case BufferType::IndexBuffer: result += "index"; break; 113 | default: result += "unknown"; break; 114 | } 115 | result += ", dtype="; 116 | result += type_name(dtype); 117 | result += ", shape=["; 118 | for (size_t i = 0; i < ndim; ++i) 119 | { 120 | result += std::to_string(shape[i]); 121 | if (i + 1 < ndim) 122 | result += ", "; 123 | } 124 | result += "]]"; 125 | return result; 126 | } 127 | -------------------------------------------------------------------------------- /src/texture.cpp: -------------------------------------------------------------------------------- 1 | #include "texture.h" 2 | #include 3 | 4 | Texture::Texture(PixelFormat pixel_format, ComponentFormat component_format, int2 size, 5 | InterpolationMode min_interpolation_mode, InterpolationMode mag_interpolation_mode, WrapMode wrap_mode, 6 | uint8_t samples, uint8_t flags, bool manual_mipmapping) : 7 | m_pixel_format(pixel_format), 8 | m_component_format(component_format), m_min_interpolation_mode(min_interpolation_mode), 9 | m_mag_interpolation_mode(mag_interpolation_mode), m_wrap_mode(wrap_mode), m_samples(samples), m_flags(flags), 10 | m_size(size), m_manual_mipmapping(manual_mipmapping) 11 | { 12 | init(); 13 | } 14 | 15 | size_t Texture::bytes_per_pixel() const 16 | { 17 | size_t result = 0; 18 | switch (m_component_format) 19 | { 20 | case ComponentFormat::UInt8: result = 1; break; 21 | case ComponentFormat::Int8: result = 1; break; 22 | case ComponentFormat::UInt16: result = 2; break; 23 | case ComponentFormat::Int16: result = 2; break; 24 | case ComponentFormat::UInt32: result = 4; break; 25 | case ComponentFormat::Int32: result = 4; break; 26 | case ComponentFormat::Float16: result = 2; break; 27 | case ComponentFormat::Float32: result = 4; break; 28 | default: throw std::invalid_argument("Texture::bytes_per_pixel(): invalid component format!"); 29 | } 30 | 31 | return result * channels(); 32 | } 33 | 34 | size_t Texture::channels() const 35 | { 36 | size_t result = 1; 37 | switch (m_pixel_format) 38 | { 39 | case PixelFormat::R: result = 1; break; 40 | case PixelFormat::RA: result = 2; break; 41 | case PixelFormat::RGB: result = 3; break; 42 | case PixelFormat::RGBA: result = 4; break; 43 | case PixelFormat::BGR: result = 3; break; 44 | case PixelFormat::BGRA: result = 4; break; 45 | case PixelFormat::Depth: result = 1; break; 46 | case PixelFormat::DepthStencil: result = 2; break; 47 | default: throw std::invalid_argument("Texture::channels(): invalid pixel format!"); 48 | } 49 | return result; 50 | } 51 | -------------------------------------------------------------------------------- /src/texture.h: -------------------------------------------------------------------------------- 1 | /** 2 | \file texture.h 3 | */ 4 | 5 | #pragma once 6 | 7 | #include "fwd.h" 8 | #include "traits.h" 9 | #include 10 | 11 | /** 12 | Defines an abstraction for textures that works with OpenGL, OpenGL ES, and Metal. 13 | 14 | This is adapted from NanoGUI's Texture class. Copyright follows. 15 | ---------- 16 | NanoGUI was developed by Wenzel Jakob . 17 | The widget drawing code is based on the NanoVG demo application 18 | by Mikko Mononen. 19 | 20 | All rights reserved. Use of this source code is governed by a 21 | BSD-style license that can be found in the LICENSE.txt file. 22 | */ 23 | class Texture 24 | { 25 | public: 26 | /// Overall format of the texture (e.g. luminance-only or RGBA) 27 | enum class PixelFormat : uint8_t 28 | { 29 | R, ///< Single-channel bitmap 30 | RA, ///< Two-channel bitmap 31 | RGB, ///< RGB bitmap 32 | RGBA, ///< RGB bitmap + alpha channel 33 | BGR, ///< BGR bitmap 34 | BGRA, ///< BGR bitmap + alpha channel 35 | Depth, ///< Depth map 36 | DepthStencil ///< Combined depth + stencil map 37 | }; 38 | 39 | /// Number format of pixel components 40 | enum class ComponentFormat : uint8_t 41 | { 42 | // Signed and unsigned integer formats 43 | UInt8 = (uint8_t)VariableType::UInt8, 44 | Int8 = (uint8_t)VariableType::Int8, 45 | UInt16 = (uint16_t)VariableType::UInt16, 46 | Int16 = (uint16_t)VariableType::Int16, 47 | UInt32 = (uint32_t)VariableType::UInt32, 48 | Int32 = (uint32_t)VariableType::Int32, 49 | 50 | // Floating point formats 51 | Float16 = (uint16_t)VariableType::Float16, 52 | Float32 = (uint32_t)VariableType::Float32 53 | }; 54 | 55 | /// Texture interpolation mode 56 | enum class InterpolationMode : uint8_t 57 | { 58 | Nearest, ///< Nearest neighbor interpolation 59 | Bilinear, ///< Bilinear interpolation 60 | Trilinear ///< Trilinear interpolation (using MIP mapping) 61 | }; 62 | 63 | /// How should out-of-bounds texture evaluations be handled? 64 | enum class WrapMode : uint8_t 65 | { 66 | ClampToEdge, ///< Clamp evaluations to the edge of the texture 67 | Repeat, ///< Repeat the texture 68 | MirrorRepeat, ///< Repeat, but flip the texture after crossing the boundary 69 | }; 70 | 71 | /// How will the texture be used? (Must specify at least one) 72 | enum TextureFlags : uint8_t 73 | { 74 | ShaderRead = 0x01, ///< Texture to be read in shaders 75 | RenderTarget = 0x02 ///< Target framebuffer for rendering 76 | }; 77 | 78 | /** 79 | Allocate memory for a texture with the given configuration 80 | 81 | \note 82 | Certain combinations of pixel and component formats may not be natively supported by the hardware. In this 83 | case, \ref init() chooses a similar supported configuration that can subsequently be queried using \ref 84 | pixel_format() and \ref component_format(). Some caution must be exercised in this case, since \ref upload() 85 | will need to provide the data in a different storage format. 86 | */ 87 | Texture(PixelFormat pixel_format, ComponentFormat component_format, int2 size, 88 | InterpolationMode min_interpolation_mode = InterpolationMode::Bilinear, 89 | InterpolationMode mag_interpolation_mode = InterpolationMode::Bilinear, 90 | WrapMode wrap_mode = WrapMode::ClampToEdge, uint8_t samples = 1, uint8_t flags = TextureFlags::ShaderRead, 91 | bool manual_mipmapping = false); 92 | 93 | /// Release all resources 94 | virtual ~Texture(); 95 | 96 | /// Return the pixel format 97 | PixelFormat pixel_format() const { return m_pixel_format; } 98 | 99 | /// Return the component format 100 | ComponentFormat component_format() const { return m_component_format; } 101 | 102 | /// Return the interpolation mode for minification 103 | InterpolationMode min_interpolation_mode() const { return m_min_interpolation_mode; } 104 | 105 | /// Return the interpolation mode for magnification 106 | InterpolationMode mag_interpolation_mode() const { return m_mag_interpolation_mode; } 107 | 108 | /// Return the wrap mode 109 | WrapMode wrap_mode() const { return m_wrap_mode; } 110 | 111 | /// Return the number of samples (MSAA) 112 | uint8_t samples() const { return m_samples; } 113 | 114 | /// Return a combination of flags (from \ref Texture::TextureFlags) 115 | uint8_t flags() const { return m_flags; } 116 | 117 | /// Return the size of this texture 118 | const int2 &size() const { return m_size; } 119 | 120 | /// Return the number of bytes consumed per pixel of this texture 121 | size_t bytes_per_pixel() const; 122 | 123 | /// Return the number of channels of this texture 124 | size_t channels() const; 125 | 126 | /// Upload packed pixel data from the CPU to the GPU 127 | void upload(const uint8_t *data); 128 | 129 | /// Upload packed pixel data to a rectangular sub-region of the texture from the CPU to the GPU 130 | void upload_sub_region(const uint8_t *data, const int2 &origin, const int2 &size); 131 | 132 | /// Download packed pixel data from the GPU to the CPU 133 | void download(uint8_t *data); 134 | 135 | /// Resize the texture (discards the current contents) 136 | void resize(const int2 &size); 137 | 138 | /// Generates the mipmap. Done automatically upon upload if manual mipmapping is disabled. 139 | void generate_mipmap(); 140 | 141 | #if defined(HELLOIMGUI_HAS_OPENGL) 142 | uint32_t texture_handle() const { return m_texture_handle; } 143 | uint32_t renderbuffer_handle() const { return m_renderbuffer_handle; } 144 | #elif defined(HELLOIMGUI_HAS_METAL) 145 | void *texture_handle() const { return m_texture_handle; } 146 | void *sampler_state_handle() const { return m_sampler_state_handle; } 147 | #endif 148 | 149 | protected: 150 | /// Initialize the texture handle 151 | void init(); 152 | 153 | protected: 154 | PixelFormat m_pixel_format; 155 | ComponentFormat m_component_format; 156 | InterpolationMode m_min_interpolation_mode; 157 | InterpolationMode m_mag_interpolation_mode; 158 | WrapMode m_wrap_mode; 159 | uint8_t m_samples; 160 | uint8_t m_flags; 161 | int2 m_size; 162 | bool m_manual_mipmapping; 163 | 164 | #if defined(HELLOIMGUI_HAS_OPENGL) 165 | uint32_t m_texture_handle = 0; 166 | uint32_t m_renderbuffer_handle = 0; 167 | #elif defined(HELLOIMGUI_HAS_METAL) 168 | void *m_texture_handle = nullptr; 169 | void *m_sampler_state_handle = nullptr; 170 | #endif 171 | }; 172 | -------------------------------------------------------------------------------- /src/timer.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Wojciech Jarosz. All rights reserved. 3 | // Use of this source code is governed by a BSD-style license that can 4 | // be found in the LICENSE.txt file. 5 | // 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | //! Simple timer with millisecond precision 12 | /*! 13 | This class is convenient for collecting performance data 14 | */ 15 | class Timer 16 | { 17 | public: 18 | //! Create a new timer and reset it 19 | Timer() { reset(); } 20 | 21 | //! Reset the timer to the current time 22 | void reset() { start = std::chrono::system_clock::now(); } 23 | 24 | //! Return the number of milliseconds elapsed since the timer was last reset 25 | double elapsed() const 26 | { 27 | auto now = std::chrono::system_clock::now(); 28 | auto duration = std::chrono::duration_cast(now - start); 29 | return (double)duration.count(); 30 | } 31 | 32 | //! Return the number of milliseconds elapsed since the timer was last reset and then reset it 33 | double lap() 34 | { 35 | auto now = std::chrono::system_clock::now(); 36 | auto duration = std::chrono::duration_cast(now - start); 37 | start = now; 38 | return (double)duration.count(); 39 | } 40 | 41 | private: 42 | std::chrono::system_clock::time_point start; 43 | }; 44 | -------------------------------------------------------------------------------- /src/traits.h: -------------------------------------------------------------------------------- 1 | /* 2 | NanoGUI was developed by Wenzel Jakob . 3 | The widget drawing code is based on the NanoVG demo application 4 | by Mikko Mononen. 5 | 6 | All rights reserved. Use of this source code is governed by a 7 | BSD-style license that can be found in the LICENSE.txt file. 8 | */ 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | 15 | /// Listing of various field types that can be used as variables in shaders 16 | enum class VariableType 17 | { 18 | Invalid = 0, 19 | Int8, 20 | UInt8, 21 | Int16, 22 | UInt16, 23 | Int32, 24 | UInt32, 25 | Int64, 26 | UInt64, 27 | Float16, 28 | Float32, 29 | Float64, 30 | Bool 31 | }; 32 | 33 | /// Convert from a C++ type to an element of \ref VariableType 34 | #if defined(_MSC_VER) 35 | #pragma warning(push) 36 | #pragma warning(disable : 4702) 37 | #endif 38 | template 39 | constexpr VariableType get_type() 40 | { 41 | if constexpr (std::is_same_v) 42 | return VariableType::Bool; 43 | 44 | if constexpr (std::is_integral_v) 45 | { 46 | if constexpr (sizeof(T) == 1) 47 | return std::is_signed_v ? VariableType::Int8 : VariableType::UInt8; 48 | else if constexpr (sizeof(T) == 2) 49 | return std::is_signed_v ? VariableType::Int16 : VariableType::UInt16; 50 | else if constexpr (sizeof(T) == 4) 51 | return std::is_signed_v ? VariableType::Int32 : VariableType::UInt32; 52 | else if constexpr (sizeof(T) == 8) 53 | return std::is_signed_v ? VariableType::Int64 : VariableType::UInt64; 54 | } 55 | else if constexpr (std::is_floating_point_v) 56 | { 57 | if constexpr (sizeof(T) == 2) 58 | return VariableType::Float16; 59 | else if constexpr (sizeof(T) == 4) 60 | return VariableType::Float32; 61 | else if constexpr (sizeof(T) == 8) 62 | return VariableType::Float64; 63 | } 64 | else 65 | { 66 | return VariableType::Invalid; 67 | } 68 | #if defined(_MSC_VER) 69 | #pragma warning(pop) 70 | #endif 71 | } 72 | 73 | /// Return the size in bytes associated with a specific variable type 74 | inline size_t type_size(VariableType type) 75 | { 76 | switch (type) 77 | { 78 | case VariableType::UInt8: 79 | case VariableType::Int8: 80 | case VariableType::Bool: return 1; 81 | 82 | case VariableType::UInt16: 83 | case VariableType::Int16: 84 | case VariableType::Float16: return 2; 85 | 86 | case VariableType::UInt32: 87 | case VariableType::Int32: 88 | case VariableType::Float32: return 4; 89 | 90 | case VariableType::UInt64: 91 | case VariableType::Int64: 92 | case VariableType::Float64: return 8; 93 | 94 | default: throw std::invalid_argument("Unknown type!"); 95 | } 96 | } 97 | 98 | /// Return the name (e.g. "uint8") associated with a specific variable type 99 | inline const char *type_name(VariableType type) 100 | { 101 | switch (type) 102 | { 103 | case VariableType::Bool: return "bool"; 104 | case VariableType::UInt8: return "uint8"; 105 | case VariableType::Int8: return "int8"; 106 | case VariableType::UInt16: return "uint16"; 107 | case VariableType::Int16: return "int16"; 108 | case VariableType::UInt32: return "uint32"; 109 | case VariableType::Int32: return "int32"; 110 | case VariableType::UInt64: return "uint64"; 111 | case VariableType::Int64: return "int64"; 112 | case VariableType::Float16: return "float16"; 113 | case VariableType::Float32: return "float32"; 114 | case VariableType::Float64: return "float64"; 115 | default: return "invalid"; 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/version.cpp.in: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Wojciech Jarosz. All rights reserved. 3 | // Use of this source code is governed by a BSD-style license that can 4 | // be found in the LICENSE.txt file. 5 | // 6 | 7 | #include 8 | 9 | using std::string; 10 | 11 | // These constants and strings are autogenerated and compiled 12 | // into the project by cmake 13 | // clang-format off 14 | int version_major() { return @VERSION_MAJOR@; } 15 | int version_minor() { return @VERSION_MINOR@; } 16 | int version_patch() { return @VERSION_PATCH@; } 17 | int version_combined() { return version_patch() + 100 * (version_minor() + 100 * version_major()); } 18 | string version() { return string("@VERSION_LONG@"); } 19 | string git_hash() { return string("@GIT_HASH@"); } 20 | string git_describe() { return string("@GIT_DESCRIBE@"); } 21 | string build_timestamp() { return string("@BUILD_TIME@"); } 22 | string backend() 23 | { 24 | #if defined(HELLOIMGUI_USE_GLFW3) 25 | return "GLFW 3"; 26 | #elif defined(HELLOIMGUI_USE_SDL2) 27 | return "SDL 2"; 28 | #endif 29 | } 30 | // clang-format on -------------------------------------------------------------------------------- /src/version.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Wojciech Jarosz. All rights reserved. 3 | // Use of this source code is governed by a BSD-style license that can 4 | // be found in the LICENSE.txt file. 5 | // 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | int version_major(); 12 | int version_minor(); 13 | int version_patch(); 14 | int version_combined(); 15 | std::string version(); 16 | std::string git_hash(); 17 | std::string git_describe(); 18 | std::string build_timestamp(); 19 | std::string backend(); --------------------------------------------------------------------------------