├── .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 | [](https://github.com/wkjarosz/hdrview/actions/workflows/ci-mac.yml)
5 | [](https://github.com/wkjarosz/hdrview/actions/workflows/ci-linux.yml)
6 | [](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 | 
17 |
18 | Or, running on an iPad as a webapp, viewing a luminance-chroma EXR image stored using XYZ primaries with chroma subsampling:
19 | 
20 |
21 | When sufficiently zoomed in, HDRView can overlay the pixel grid and numeric color values on each pixel to facilitate inspection:
22 | 
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 | 
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 | 
31 |
32 | This reduces apparent banding artifacts in smooth gradients compared to naively displaying HDR images on such displays:
33 | 
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