├── resources ├── icon.icns ├── icon.ico ├── icon.pdf ├── icon.psd ├── icon-128.png ├── icon-16.png ├── icon-256.png ├── icon-32.png ├── icon-512.png ├── icon-64.png ├── gamma-grid.exr ├── gamma-grid.png ├── icon-1024.png ├── screenshot0.png ├── screenshot1.png ├── screenshot2.png ├── screenshot3.png ├── screenshot4.png ├── hdrview.desktop ├── osx-post-install.cmake ├── icon.rc ├── create-exe.bat └── MacOSXBundleInfo.plist.in ├── src ├── PFM.h ├── PPM.h ├── EditImagePanel.h ├── HelpWindow.h ├── Well.cpp ├── HSLGradient.h ├── Well.h ├── ImageShader.h ├── HSLGradient.cpp ├── ParallelFor.cpp ├── Timer.h ├── ParallelFor.h ├── Range.h ├── Color.cpp ├── Fwd.h ├── Progress.cpp ├── EnvMap.h ├── HDRViewer.h ├── FilmicToneCurve.h ├── Async.h ├── MultiGraph.h ├── forced-random-dither.cpp ├── Progress.h ├── ImageButton.h ├── Colorspace.h ├── CommandHistory.h ├── Common.cpp ├── ImageListPanel.h ├── PFM.cpp ├── HDRView.cpp ├── HelpWindow.cpp ├── PPM.cpp ├── GLImage.h ├── FilmicToneCurve.cpp ├── MultiGraph.cpp ├── HDRImageViewer.h ├── EnvMap.cpp ├── ImageButton.cpp ├── Common.h ├── GLImage.cpp └── HDRImage.h ├── .gitignore ├── .appveyor.yml ├── bitbucket-pipelines.yml ├── .gitmodules ├── TODO.md ├── LICENSE.txt ├── README.md ├── ext └── CMakeLists.txt └── CMakeLists.txt /resources/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdcseacave/HDRView/HEAD/resources/icon.icns -------------------------------------------------------------------------------- /resources/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdcseacave/HDRView/HEAD/resources/icon.ico -------------------------------------------------------------------------------- /resources/icon.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdcseacave/HDRView/HEAD/resources/icon.pdf -------------------------------------------------------------------------------- /resources/icon.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdcseacave/HDRView/HEAD/resources/icon.psd -------------------------------------------------------------------------------- /resources/icon-128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdcseacave/HDRView/HEAD/resources/icon-128.png -------------------------------------------------------------------------------- /resources/icon-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdcseacave/HDRView/HEAD/resources/icon-16.png -------------------------------------------------------------------------------- /resources/icon-256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdcseacave/HDRView/HEAD/resources/icon-256.png -------------------------------------------------------------------------------- /resources/icon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdcseacave/HDRView/HEAD/resources/icon-32.png -------------------------------------------------------------------------------- /resources/icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdcseacave/HDRView/HEAD/resources/icon-512.png -------------------------------------------------------------------------------- /resources/icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdcseacave/HDRView/HEAD/resources/icon-64.png -------------------------------------------------------------------------------- /resources/gamma-grid.exr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdcseacave/HDRView/HEAD/resources/gamma-grid.exr -------------------------------------------------------------------------------- /resources/gamma-grid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdcseacave/HDRView/HEAD/resources/gamma-grid.png -------------------------------------------------------------------------------- /resources/icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdcseacave/HDRView/HEAD/resources/icon-1024.png -------------------------------------------------------------------------------- /resources/screenshot0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdcseacave/HDRView/HEAD/resources/screenshot0.png -------------------------------------------------------------------------------- /resources/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdcseacave/HDRView/HEAD/resources/screenshot1.png -------------------------------------------------------------------------------- /resources/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdcseacave/HDRView/HEAD/resources/screenshot2.png -------------------------------------------------------------------------------- /resources/screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdcseacave/HDRView/HEAD/resources/screenshot3.png -------------------------------------------------------------------------------- /resources/screenshot4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cdcseacave/HDRView/HEAD/resources/screenshot4.png -------------------------------------------------------------------------------- /resources/hdrview.desktop: -------------------------------------------------------------------------------- 1 | [Desktop Entry] 2 | Type=Application 3 | Version=1.0 4 | Name=hdrview 5 | GenericName=Image Viewer 6 | Comment=High dynamic range (HDR) image viewer and comparison tool/ 7 | Exec=hdrview %F 8 | Icon=hdrview.png 9 | Terminal=false 10 | MimeType=image/*; 11 | Categories=Graphics; 12 | -------------------------------------------------------------------------------- /resources/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 | -------------------------------------------------------------------------------- /src/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 | bool isPFMImage(const char *filename) noexcept; 10 | bool writePFMImage(const char *filename, int width, int height, int numChannels, const float *data); 11 | float * loadPFMImage(const char *filename, int *width, int *height, int *numChannels); 12 | -------------------------------------------------------------------------------- /src/PPM.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 | bool isPPMImage(const char *filename); 10 | bool writePPMImage(const char *filename, int width, int height, int numChannels, const unsigned char *data); 11 | float * loadPPMImage(const char *filename, int *width, int *height, int *numChannels); 12 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /.appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 1.0.{build} 2 | branches: 3 | only: 4 | - master 5 | - develop 6 | os: Visual Studio 2017 7 | configuration: 8 | - Debug 9 | - Release 10 | platform: 11 | - x64 12 | clone_folder: C:\projects\hdrview 13 | install: 14 | - git submodule update --init --recursive 15 | before_build: 16 | - cmake -DCMAKE_BUILD_TYPE=%Configuration% -DBOOST_ROOT=C:\Libraries\boost_1_64_0 -G "Visual Studio 15 2017 Win64" . 17 | build_script: 18 | - cmake --build . --target ALL_BUILD --config %Configuration% --parallel 4 19 | on_success: 20 | - cmd: 7z a HDRView_x64.7z "C:\projects\hdrview\%Configuration%\*.exe" 21 | - cmd: appveyor PushArtifact HDRView_x64.7z 22 | test: off 23 | -------------------------------------------------------------------------------- /bitbucket-pipelines.yml: -------------------------------------------------------------------------------- 1 | # This is a sample build configuration for C++ � Make. 2 | # Check our guides at https://confluence.atlassian.com/x/5Q4SMw for more examples. 3 | # Only use spaces to indent your .yml configuration. 4 | # ----- 5 | # You can specify a custom docker image from Docker Hub as your build environment. 6 | image: ubuntu:18.04 7 | 8 | pipelines: 9 | default: 10 | - step: 11 | script: 12 | - apt-get -qq -y --force-yes update 13 | - apt-get -y --force-yes install git build-essential cmake cmake-data xorg-dev libglu1-mesa-dev libxrandr-dev 14 | - git submodule update --init --recursive 15 | - mkdir build 16 | - cd build 17 | - cmake .. 18 | - make -j2 -------------------------------------------------------------------------------- /resources/icon.rc: -------------------------------------------------------------------------------- 1 | id ICON "icon.ico" 2 | GLFW_ICON ICON "icon.ico" 3 | 4 | 1 VERSIONINFO 5 | FILEVERSION 1,0,0,0 6 | PRODUCTVERSION 1,0,0,0 7 | BEGIN 8 | BLOCK "StringFileInfo" 9 | BEGIN 10 | BLOCK "080904E4" 11 | BEGIN 12 | VALUE "CompanyName", "Dartmouth College" 13 | VALUE "FileDescription", "HDRView" 14 | VALUE "FileVersion", "1.0" 15 | VALUE "InternalName", "HDRView" 16 | VALUE "LegalCopyright", "Wojciech Jarosz" 17 | VALUE "OriginalFilename", "HDRView.exe" 18 | VALUE "ProductName", "HDRView" 19 | VALUE "ProductVersion", "1.0" 20 | END 21 | END 22 | 23 | BLOCK "VarFileInfo" 24 | BEGIN 25 | VALUE "Translation", 0x809, 1252 26 | END 27 | END 28 | -------------------------------------------------------------------------------- /src/EditImagePanel.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 "Fwd.h" 11 | 12 | using namespace nanogui; 13 | 14 | class EditImagePanel : public Widget 15 | { 16 | public: 17 | EditImagePanel(Widget *parent, HDRViewScreen * screen, ImageListPanel * imagesPanel); 18 | 19 | void draw(NVGcontext *ctx) override; 20 | 21 | private: 22 | HDRViewScreen * m_screen = nullptr; 23 | ImageListPanel * m_imagesPanel = nullptr; 24 | Button * m_undoButton = nullptr; 25 | Button * m_redoButton = nullptr; 26 | std::vector m_filterButtons; 27 | 28 | public: 29 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW 30 | }; -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ext/tinydir"] 2 | path = ext/tinydir 3 | url = https://github.com/cxong/tinydir.git 4 | [submodule "ext/docopt"] 5 | path = ext/docopt 6 | url = https://github.com/docopt/docopt.cpp.git 7 | [submodule "ext/spdlog"] 8 | path = ext/spdlog 9 | url = https://github.com/gabime/spdlog.git 10 | [submodule "ext/tinydngloader"] 11 | path = ext/tinydngloader 12 | url = https://github.com/syoyo/tinydngloader.git 13 | [submodule "ext/openexr"] 14 | path = ext/openexr 15 | url = https://github.com/mitsuba-renderer/openexr.git 16 | branch = master 17 | [submodule "ext/zlib"] 18 | path = ext/zlib 19 | url = https://github.com/mitsuba-renderer/zlib.git 20 | branch = master 21 | [submodule "ext/nanogui"] 22 | path = ext/nanogui 23 | url = https://github.com/wjakob/nanogui.git 24 | [submodule "ext/tinynpy"] 25 | path = ext/tinynpy 26 | url = https://github.com/cdcseacave/TinyNPY.git 27 | -------------------------------------------------------------------------------- /src/HelpWindow.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 | // Adapted from tev: 7 | // This file was developed by Thomas Müller . 8 | // It is published under the BSD 3-Clause License within the LICENSE file. 9 | 10 | #pragma once 11 | 12 | #include "Common.h" 13 | 14 | #include 15 | 16 | #include 17 | 18 | class HelpWindow : public nanogui::Window 19 | { 20 | public: 21 | HelpWindow(nanogui::Widget* parent, std::function closeCallback); 22 | 23 | bool keyboardEvent(int key, int scancode, int action, int modifiers) override; 24 | 25 | static std::string COMMAND; 26 | static std::string ALT; 27 | 28 | private: 29 | std::function mCloseCallback; 30 | }; 31 | -------------------------------------------------------------------------------- /resources/create-exe.bat: -------------------------------------------------------------------------------- 1 | @echo off 2 | 3 | set cwd=%cd% 4 | cd /D %~dp0 5 | 6 | set DevCmd="C:\Program Files (x86)\Microsoft Visual Studio 14.0\Common7\Tools\VsDevCmd.bat" 7 | set MSBuildOptions=/v:m /p:Configuration=Release 8 | set BuildDir64="build-exe-64" 9 | set BuildDir32="build-exe-32" 10 | 11 | call %DevCmd% 12 | 13 | echo Building 64-bit hdrview... 14 | mkdir %BuildDir64% 15 | cd %BuildDir64% 16 | cmake -DHDRVIEW_DEPLOY=1 -G "Visual Studio 15 2017 Win64" ..\.. 17 | msbuild %MSBuildOptions% hdrview.sln 18 | move "Release\HDRView.exe" "..\..\HDRView.exe" 19 | cd .. 20 | rmdir /S /Q %BuildDir64% 21 | 22 | echo Building 32-bit hdrview... 23 | mkdir %BuildDir32% 24 | cd %BuildDir32% 25 | cmake -DHDRVIEW_DEPLOY=1 -G "Visual Studio 15 2017" ..\.. 26 | msbuild %MSBuildOptions% hdrview.sln 27 | move "Release\HDRView.exe" "..\..\HDRView-32bit.exe" 28 | cd .. 29 | rmdir /S /Q %BuildDir32% 30 | 31 | echo Returning to original directory. 32 | cd /D %cwd% 33 | pause -------------------------------------------------------------------------------- /src/Well.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 "Well.h" 8 | #include 9 | 10 | using namespace nanogui; 11 | 12 | Well::Well(Widget *parent, float radius, const Color & inner, const Color & outer) 13 | : Widget(parent), m_radius(radius), m_innerColor(inner), m_outerColor(outer) 14 | { 15 | 16 | } 17 | 18 | void Well::draw(NVGcontext* ctx) 19 | { 20 | NVGpaint paint = nvgBoxGradient(ctx, mPos.x() + 1, mPos.y() + 1, 21 | mSize.x()-2, mSize.y()-2, m_radius, m_radius+1, 22 | m_innerColor, m_outerColor); 23 | nvgBeginPath(ctx); 24 | nvgRoundedRect(ctx, mPos.x(), mPos.y(), mSize.x(), mSize.y(), m_radius); 25 | nvgFillPaint(ctx, paint); 26 | nvgFill(ctx); 27 | 28 | Widget::draw(ctx); 29 | } -------------------------------------------------------------------------------- /src/HSLGradient.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 | #include "Common.h" 8 | #include 9 | 10 | class HSLGradient : public nanogui::Widget 11 | { 12 | public: 13 | HSLGradient(Widget *parent); 14 | 15 | void setHueOffset(float offset) { mHue = offset; } 16 | float hueOffset() const { return mHue; } 17 | 18 | void setSaturation(float s) { mSaturation = s; } 19 | float saturation() const { return mSaturation; } 20 | 21 | void setLightness(float l) { mLightness = l; } 22 | float lightness() const { return mLightness; } 23 | 24 | virtual Eigen::Vector2i preferredSize(NVGcontext *ctx) const override; 25 | virtual void draw(NVGcontext *ctx) override; 26 | 27 | protected: 28 | float mHue = 0.0f; 29 | float mSaturation = 0.5f; 30 | float mLightness = 0.5f; 31 | 32 | public: 33 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW 34 | }; 35 | -------------------------------------------------------------------------------- /src/Well.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Wojciech Jarosz . All rights reserved. 3 | // Use of this source code is governed by a BSD-style license that can 4 | // be found in the LICENSE.txt file. 5 | // 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | class Well : public nanogui::Widget 12 | { 13 | public: 14 | Well(nanogui::Widget *parent, float radius = 3.0f, 15 | const nanogui::Color & inner = nanogui::Color(0, 32), 16 | const nanogui::Color & outer = nanogui::Color(0, 92)); 17 | 18 | /// Return the inner well color 19 | const nanogui::Color & innerColor() const { return m_innerColor; } 20 | /// Set the inner well color 21 | void setInnerColor(const nanogui::Color & innerColor) { m_innerColor = innerColor; } 22 | 23 | /// Return the outer well color 24 | const nanogui::Color & outerColor() const { return m_outerColor; } 25 | /// Set the outer well color 26 | void setOuterColor(const nanogui::Color & outerColor) { m_outerColor = outerColor; } 27 | 28 | void draw(NVGcontext* ctx) override; 29 | 30 | protected: 31 | float m_radius; 32 | nanogui::Color m_innerColor, m_outerColor; 33 | 34 | public: 35 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW 36 | }; 37 | -------------------------------------------------------------------------------- /src/ImageShader.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 "Common.h" 12 | 13 | /*! 14 | * Draws an image to the screen, optionally with high-quality dithering. 15 | */ 16 | class ImageShader 17 | { 18 | public: 19 | ImageShader(); 20 | virtual ~ImageShader(); 21 | 22 | void draw(GLuint imageId, 23 | const Eigen::Vector2f & scale, 24 | const Eigen::Vector2f & position, 25 | float gain, float gamma, 26 | bool sRGB, bool dither, 27 | EChannel channel, EBlendMode mode); 28 | 29 | void draw(GLuint imageId, 30 | GLuint referenceId, 31 | const Eigen::Vector2f & imageScale, 32 | const Eigen::Vector2f & imagePosition, 33 | const Eigen::Vector2f & referenceScale, 34 | const Eigen::Vector2f & referencePosition, 35 | float gain, float gamma, 36 | bool sRGB, bool dither, 37 | EChannel channel, EBlendMode mode); 38 | 39 | private: 40 | nanogui::GLShader m_shader; 41 | GLuint m_ditherTexId = 0; 42 | }; -------------------------------------------------------------------------------- /src/HSLGradient.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 "HSLGradient.h" 8 | #include 9 | 10 | using namespace nanogui; 11 | using namespace std; 12 | 13 | 14 | HSLGradient::HSLGradient(Widget *parent) 15 | : Widget(parent) 16 | { 17 | 18 | } 19 | 20 | Vector2i HSLGradient::preferredSize(NVGcontext *) const 21 | { 22 | return { 100, 10. }; 23 | } 24 | 25 | void HSLGradient::draw(NVGcontext *ctx) 26 | { 27 | Widget::draw(ctx); 28 | 29 | if (!mVisible) 30 | return; 31 | 32 | float offset = mod(mHue/60.f, 6.f); 33 | float integer = floor(offset); 34 | float remainder = offset - integer; 35 | for (int i = -1; i < 6; i++) 36 | { 37 | float x0 = mPos.x() + (i + remainder) / 6.f * mSize.x(); 38 | float x1 = mPos.x() + (i + remainder + 1.f) / 6.f * mSize.x(); 39 | 40 | NVGpaint paint = nvgLinearGradient(ctx, x0, 0, x1, 0, 41 | nvgHSL((i - integer) / 6.f, mSaturation, mLightness), 42 | nvgHSL((i - integer + 1.f) / 6.f, mSaturation, mLightness)); 43 | nvgBeginPath(ctx); 44 | nvgRect(ctx, std::floor(x0), mPos.y(), std::ceil(mSize.x() / 6.f), mSize.y()); 45 | nvgFillPaint(ctx, paint); 46 | nvgFill(ctx); 47 | } 48 | } -------------------------------------------------------------------------------- /src/ParallelFor.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 "ParallelFor.h" 8 | #include 9 | #include 10 | 11 | using namespace std; 12 | 13 | // adapted from http://www.andythomason.com/2016/08/21/c-multithreading-an-effective-parallel-for-loop/ 14 | // license unknown, presumed public domain 15 | void parallel_for(int begin, int end, int step, function body, bool serial) 16 | { 17 | atomic nextIndex; 18 | nextIndex = begin; 19 | 20 | auto policy = serial ? std::launch::deferred : std::launch::async; 21 | size_t numCPUs = thread::hardware_concurrency(); 22 | vector< future > futures(numCPUs); 23 | for (size_t cpu = 0; cpu != numCPUs; ++cpu) 24 | { 25 | futures[cpu] = async( 26 | policy, 27 | [cpu, &nextIndex, end, step, &body]() 28 | { 29 | // just iterate, grabbing the next available atomic index in the range [begin, end) 30 | while (true) 31 | { 32 | int i = nextIndex.fetch_add(step); 33 | if (i >= end) break; 34 | body(i, cpu); 35 | } 36 | }); 37 | } 38 | for (auto & f : futures) 39 | f.get(); 40 | } 41 | 42 | void parallel_for(int begin, int end, int step, function body, bool serial) 43 | { 44 | parallel_for(begin, end, step, [&body](int i, size_t){body(i);}, serial); 45 | } -------------------------------------------------------------------------------- /src/Timer.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Wojciech Jarosz . All rights reserved. 3 | // Use of this source code is governed by a BSD-style license that can 4 | // be found in the LICENSE.txt file. 5 | // 6 | 7 | #pragma once 8 | 9 | #include "Common.h" 10 | #include 11 | 12 | //! Simple timer with millisecond precision 13 | /*! 14 | This class is convenient for collecting performance data 15 | */ 16 | class Timer 17 | { 18 | public: 19 | //! Create a new timer and reset it 20 | Timer() 21 | { 22 | reset(); 23 | } 24 | 25 | //! Reset the timer to the current time 26 | void reset() 27 | { 28 | start = std::chrono::system_clock::now(); 29 | } 30 | 31 | //! Return the number of milliseconds elapsed since the timer was last reset 32 | double elapsed() const 33 | { 34 | auto now = std::chrono::system_clock::now(); 35 | auto duration = std::chrono::duration_cast(now - start); 36 | return (double) duration.count(); 37 | } 38 | 39 | //! Return the number of milliseconds elapsed since the timer was last reset and then reset it 40 | double lap() 41 | { 42 | auto now = std::chrono::system_clock::now(); 43 | auto duration = std::chrono::duration_cast(now - start); 44 | start = now; 45 | return (double) duration.count(); 46 | } 47 | 48 | private: 49 | std::chrono::system_clock::time_point start; 50 | }; 51 | -------------------------------------------------------------------------------- /src/ParallelFor.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Wojciech Jarosz . All rights reserved. 3 | // Use of this source code is governed by a BSD-style license that can 4 | // be found in the LICENSE.txt file. 5 | // 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | /*! 12 | * @brief Executes the body of a for loop in parallel 13 | * @param begin The starting index of the for loop 14 | * @param end One past the ending index of the for loop 15 | * @param step How much to increment at each iteration when moving from begin to end 16 | * @param body The body of the for loop as a lambda, taking two parameters: the iterator index in [begin,end), and the CPU number 17 | * @param serial Force the loop to execute in serial instead of parallel 18 | */ 19 | void parallel_for(int begin, int end, int step, std::function body, bool serial = false); 20 | 21 | /*! 22 | * @brief A version of the parallel_for accepting a body lambda that only takes the iterator index as a parameter 23 | */ 24 | void parallel_for(int begin, int end, int step, std::function body, bool serial = false); 25 | 26 | 27 | 28 | // adapted from http://www.andythomason.com/2016/08/21/c-multithreading-an-effective-parallel-for-loop/ 29 | // license unknown, presumed public domain 30 | inline void parallel_for(int begin, int end, std::function body, bool serial = false) 31 | { 32 | parallel_for(begin, end, 1, body); 33 | } 34 | 35 | inline void parallel_for(int begin, int end, std::function body, bool serial = false) 36 | { 37 | parallel_for(begin, end, 1, body, serial); 38 | } -------------------------------------------------------------------------------- /src/Range.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 | /*! 10 | * @class Range range.h 11 | * @brief Python-style range: iterates from min to max in range-based for loops 12 | * 13 | * To use: 14 | * @code 15 | * for(int i = 0; i < 100; i++) { ... } // old way 16 | * for(auto i : range(100)) { ... } // new way 17 | * 18 | * for(int i = 10; i < 100; i+=2) { ... } // old way 19 | * for(auto i : range(10, 100, 2)) { ... } // new way 20 | * 21 | * for(float i = 3.5f; i > 1.5f; i-=0.01f) { ... } // old way 22 | * for(auto i : range(3.5f, 1.5f, -0.01f)) { ... } // new way 23 | * @endcode 24 | */ 25 | template 26 | class Range 27 | { 28 | public: 29 | class Iterator 30 | { 31 | public: 32 | Iterator(T pos, T step) : m_pos(pos), m_step(step) {} 33 | 34 | bool operator!=(const Iterator &o) const { return (o.m_pos - m_pos) * m_step > T(0);} 35 | Iterator &operator++() {m_pos += m_step; return *this;} 36 | Iterator operator++(int) {Iterator copy(*this); operator++(); return copy;} 37 | T operator*() const {return m_pos;} 38 | private: 39 | T m_pos, m_step; 40 | }; 41 | 42 | Range(T start, T end, T step = T(1)) 43 | : m_start(start), m_end(end), m_step(step) {} 44 | 45 | Iterator begin() const {return Iterator(m_start, m_step);} 46 | Iterator end() const {return Iterator(m_end, m_step);} 47 | 48 | private: 49 | T m_start, m_end, m_step; 50 | }; 51 | 52 | template 53 | Range range(T end) {return Range(T(0), end, T(1));} 54 | 55 | template 56 | Range range(T start, T end, T step = T(1)) {return Range(start, end, step);} 57 | 58 | -------------------------------------------------------------------------------- /src/Color.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 "Color.h" 8 | #include "Colorspace.h" 9 | 10 | 11 | #define COLORSPACE_FUNCTION_WRAPPER(FUNC) \ 12 | Color3 Color3::FUNC() const \ 13 | { \ 14 | Color3 ret; \ 15 | ::FUNC(&ret.r, &ret.g, &ret.b, r, g, b); \ 16 | return ret; \ 17 | } \ 18 | 19 | COLORSPACE_FUNCTION_WRAPPER(LinearSRGBToXYZ) 20 | COLORSPACE_FUNCTION_WRAPPER(XYZToLinearSRGB) 21 | COLORSPACE_FUNCTION_WRAPPER(LinearAdobeRGBToXYZ) 22 | COLORSPACE_FUNCTION_WRAPPER(XYZToLinearAdobeRGB) 23 | COLORSPACE_FUNCTION_WRAPPER(XYZToLab) 24 | COLORSPACE_FUNCTION_WRAPPER(LabToXYZ) 25 | COLORSPACE_FUNCTION_WRAPPER(XYZToLuv) 26 | COLORSPACE_FUNCTION_WRAPPER(LuvToXYZ) 27 | COLORSPACE_FUNCTION_WRAPPER(RGBToHSV) 28 | COLORSPACE_FUNCTION_WRAPPER(HSVToRGB) 29 | COLORSPACE_FUNCTION_WRAPPER(RGBToHSL) 30 | COLORSPACE_FUNCTION_WRAPPER(HSLToRGB) 31 | 32 | Color3 Color3::xyYToXYZ() const 33 | { 34 | Color3 ret; 35 | ::xyYToXZ(&ret[0], &ret[2], r, g, b); 36 | ret.g = g; 37 | return ret; 38 | } 39 | Color3 Color3::XYZToxyY() const 40 | { 41 | Color3 ret; 42 | ::XYZToxy(&ret[0], &ret[1], r, g, b); 43 | ret.b = b; 44 | return ret; 45 | } 46 | Color3 Color3::HSIAdjust(float h, float s, float i) const 47 | { 48 | Color3 ret(r,g,b); 49 | ::HSIAdjust(&ret[0], &ret[1], &ret[2], h, s, i); 50 | return ret; 51 | } 52 | Color3 Color3::HSLAdjust(float h, float s, float l) const 53 | { 54 | Color3 ret(r,g,b); 55 | ::HSLAdjust(&ret[0], &ret[1], &ret[2], h, s, l); 56 | return ret; 57 | } 58 | Color3 Color3::convert(EColorSpace dst, EColorSpace src) const 59 | { 60 | Color3 ret; 61 | convertColorSpace(dst, &ret[0], &ret[1], &ret[2], src, r, g, b); 62 | return ret; 63 | } -------------------------------------------------------------------------------- /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 | // forward declarations 10 | class Color3; 11 | class Color4; 12 | class ImageButton; 13 | class ImageCommandUndo; 14 | class FullImageUndo; 15 | class LambdaUndo; 16 | class CommandHistory; 17 | class GLImage; 18 | class HDRImage; 19 | class HDRViewScreen; 20 | class HDRImageViewer; 21 | class HelpWindow; 22 | class MultiGraph; 23 | class EditImagePanel; 24 | class HistogramPanel; 25 | class ImageListPanel; 26 | class Timer; 27 | template class Range; 28 | 29 | 30 | namespace nanogui { class Widget; } 31 | namespace nanogui { class Button; } 32 | namespace nanogui { class CheckBox; } 33 | namespace nanogui { class Label; } 34 | namespace nanogui { class MessageDialog; } 35 | namespace nanogui { class Slider; } 36 | namespace nanogui { class VScrollPanel; } 37 | namespace nanogui { class Window; } 38 | namespace nanogui { template class FloatBox; } 39 | namespace nanogui { class GLShader; } 40 | 41 | 42 | namespace spdlog { class logger; } 43 | 44 | 45 | enum EColorSpace : int 46 | { 47 | LinearSRGB_CS = 0, 48 | LinearAdobeRGB_CS, 49 | CIEXYZ_CS, 50 | CIELab_CS, 51 | CIELuv_CS, 52 | CIExyY_CS, 53 | HLS_CS, 54 | HSV_CS 55 | }; 56 | 57 | 58 | enum EChannel : int 59 | { 60 | RGB = 0, 61 | RED, 62 | GREEN, 63 | BLUE, 64 | LUMINANCE, 65 | CIE_L, 66 | CIE_a, 67 | CIE_b, 68 | CIE_CHROMATICITY, 69 | FALSE_COLOR, 70 | POSITIVE_NEGATIVE, 71 | 72 | NUM_CHANNELS 73 | }; 74 | 75 | 76 | enum EBlendMode : int 77 | { 78 | NORMAL_BLEND = 0, 79 | MULTIPLY_BLEND, 80 | DIVIDE_BLEND, 81 | ADD_BLEND, 82 | AVERAGE_BLEND, 83 | SUBTRACT_BLEND, 84 | DIFFERENCE_BLEND, 85 | RELATIVE_DIFFERENCE_BLEND, 86 | 87 | NUM_BLEND_MODES 88 | }; -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | ## TODO 2 | 3 | - [ ] Improve responsiveness during long operations 4 | - [x] Add progress bars 5 | - [x] Run them in a separate thread and avoid freezing the main application 6 | - [x] Send texture data to GL in smaller tiles, across several re-draws to avoid stalling main app 7 | - [ ] Allow canceling/aborting long operations 8 | - [ ] Refactor Color3 and Color4 classes as subclasses of Eigen Matrices, so we can more easily do color conversion. 9 | - [x] Add log-linear and log-log histogram options? 10 | - [ ] Improved DNG/demosaicing pipeline 11 | - [ ] Improve DNG color correction 12 | - [ ] Allow skipping DNG demosaicing during load 13 | - [ ] Add demosaicing/color correction/white balancing post-load filters 14 | - [ ] Will require storing DNG metadata to apply correct color-correction matrix 15 | - [ ] Selection support 16 | - [ ] More image filters/transformations/adjustments 17 | - [x] Canvas size/cropping 18 | - [ ] White balance adjustment 19 | - [x] Brightness/contrast 20 | - [ ] Luminance/chromaticity denoising 21 | - [ ] Levels 22 | - [x] Hue/Saturation 23 | - [x] Convert to grayscale/desaturate 24 | - [x] Invert 25 | - [ ] Equalize/normalize histogram 26 | - [ ] Match color/histogram matching 27 | - [ ] FFT-based convolution/blur 28 | - [ ] Motion blur 29 | - [ ] Merge down/flatten layers 30 | - [ ] 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) 31 | - [ ] HDR merging 32 | - [ ] HDR tonemapping 33 | - [ ] General image editing 34 | - [ ] Clone stamp 35 | - [ ] Airbrush 36 | - [ ] Cropping 37 | - [ ] GUI improvements 38 | - [x] Add support for resizing side panel 39 | - [ ] Allow error logging to output to a debug status panel in the GUI. 40 | - [ ] Improved drop-down menus 41 | - [ ] Save all 42 | - [x] Close all 43 | - [ ] Show command history and allow undoing/redoing multiple steps at once 44 | -------------------------------------------------------------------------------- /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 createState, float totalPercentage) : 11 | m_numSteps(1), 12 | m_percentageOfParent(totalPercentage), 13 | m_stepPercent(m_numSteps == 0 ? totalPercentage : totalPercentage / m_numSteps), 14 | m_atomicState(createState ? std::make_shared(0.f) : nullptr) 15 | { 16 | 17 | } 18 | // 19 | //AtomicProgress::AtomicProgress(AtomicPercent32 * state, float totalPercentage) : 20 | // m_numSteps(1), 21 | // m_percentageOfParent(totalPercentage), 22 | // m_stepPercent(m_numSteps == 0 ? totalPercentage : totalPercentage / m_numSteps), 23 | // m_atomicState(state), m_isStateOwner(false) 24 | //{ 25 | // 26 | //} 27 | 28 | AtomicProgress::AtomicProgress(const AtomicProgress & parent, float percentageOfParent) : 29 | m_numSteps(1), 30 | m_percentageOfParent(parent.m_percentageOfParent * percentageOfParent), 31 | m_stepPercent(m_numSteps == 0 ? m_percentageOfParent : m_percentageOfParent / m_numSteps), 32 | m_atomicState(parent.m_atomicState) 33 | { 34 | 35 | } 36 | 37 | void AtomicProgress::resetProgress(float p) 38 | { 39 | if (!m_atomicState) 40 | return; 41 | 42 | *m_atomicState = p; 43 | } 44 | 45 | float AtomicProgress::progress() const 46 | { 47 | if (!m_atomicState) 48 | return -1.f; 49 | 50 | return float(*m_atomicState); 51 | } 52 | 53 | void AtomicProgress::setAvailablePercent(float availablePercent) 54 | { 55 | m_percentageOfParent = availablePercent; 56 | m_stepPercent = m_numSteps == 0 ? availablePercent : availablePercent / m_numSteps; 57 | } 58 | 59 | void AtomicProgress::setNumSteps(int numSteps) 60 | { 61 | m_numSteps = numSteps; 62 | m_stepPercent = m_numSteps == 0 ? m_percentageOfParent : m_percentageOfParent / m_numSteps; 63 | } 64 | 65 | AtomicProgress& AtomicProgress::operator+=(int steps) 66 | { 67 | if (!m_atomicState) 68 | return *this; 69 | 70 | *m_atomicState += steps * m_stepPercent; 71 | 72 | return *this; 73 | } -------------------------------------------------------------------------------- /src/EnvMap.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 | #include 12 | #include 13 | 14 | using UV2XYZFn = Eigen::Vector3f(const Eigen::Vector2f &); 15 | using XYZ2UVFn = Eigen::Vector2f(const Eigen::Vector3f &); 16 | 17 | 18 | enum EEnvMappingUVMode : int 19 | { 20 | ANGULAR_MAP = 0, 21 | MIRROR_BALL, 22 | LAT_LONG, 23 | CYLINDRICAL, 24 | CUBE_MAP 25 | }; 26 | 27 | 28 | /*! 29 | * @brief Generic environment map conversion 30 | * 31 | * Converts from a source envmap uv parametrization \a src to 32 | * a destination envmap uv parametrization \a dst, 33 | * each specified using the \a EEnvMappingMode enumeriation 34 | * 35 | * @param dst Destination envmap parametrization 36 | * @param src Source envmap parametrization 37 | * @param srcUV 2D uv coordinates in source parametrization 38 | */ 39 | Eigen::Vector2f convertEnvMappingUV(EEnvMappingUVMode dst, EEnvMappingUVMode src, const Eigen::Vector2f & srcUV); 40 | 41 | const std::vector & envMappingNames(); 42 | 43 | // functions that convert from UV image plane coordinates to 44 | // XYZ world coordinates for the various light probe representations 45 | Eigen::Vector3f angularMapToXYZ(const Eigen::Vector2f & uv); 46 | Eigen::Vector3f mirrorBallToXYZ(const Eigen::Vector2f & uv); 47 | Eigen::Vector3f latLongToXYZ(const Eigen::Vector2f & uv); 48 | Eigen::Vector3f cylindricalToXYZ(const Eigen::Vector2f & uv); 49 | Eigen::Vector3f cubeMapToXYZ(const Eigen::Vector2f & uv); 50 | 51 | UV2XYZFn * envMapUVToXYZ(EEnvMappingUVMode mode); 52 | 53 | // functions that convert from XYZ world coordinates to 54 | // UV image plane coordinates for the various light probe representations 55 | Eigen::Vector2f XYZToAngularMap(const Eigen::Vector3f & xyz); 56 | Eigen::Vector2f XYZToMirrorBall(const Eigen::Vector3f & xyz); 57 | Eigen::Vector2f XYZToLatLong(const Eigen::Vector3f & xyz); 58 | Eigen::Vector2f XYZToCylindrical(const Eigen::Vector3f & xyz); 59 | Eigen::Vector2f XYZToCubeMap(const Eigen::Vector3f & xyz); 60 | 61 | XYZ2UVFn * XYZToEnvMapUV(EEnvMappingUVMode mode); 62 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/HDRViewer.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 | #include 13 | #include "Fwd.h" 14 | #include "CommandHistory.h" 15 | 16 | using namespace nanogui; 17 | using namespace Eigen; 18 | 19 | class HDRViewScreen : public Screen 20 | { 21 | public: 22 | HDRViewScreen(float exposure, float gamma, bool sRGB, bool dither, std::vector args); 23 | ~HDRViewScreen() override; 24 | 25 | // overridden virtual functions from Screen 26 | void drawContents() override; 27 | bool dropEvent(const std::vector &filenames) override; 28 | bool mouseButtonEvent(const Eigen::Vector2i &p, int button, bool down, int modifiers) override; 29 | bool mouseMotionEvent(const Eigen::Vector2i& p, const Eigen::Vector2i& rel, int button, int modifiers) override; 30 | bool keyboardEvent(int key, int scancode, int action, int modifiers) override; 31 | 32 | bool loadImage(); 33 | void saveImage(); 34 | void askCloseImage(int index); 35 | void askCloseAllImages(); 36 | void flipImage(bool h); 37 | void clearFocusPath() {mFocusPath.clear();} 38 | 39 | int modifiers() const {return mModifiers;} 40 | 41 | void updateCaption(); 42 | 43 | private: 44 | void toggleHelpWindow(); 45 | void updateLayout(); 46 | bool atSidePanelEdge(const Eigen::Vector2i& p) 47 | { 48 | return p.x() - m_sidePanel->fixedWidth() < 10 && p.x() - m_sidePanel->fixedWidth() > -5; 49 | } 50 | 51 | Window * m_topPanel = nullptr; 52 | Window * m_sidePanel = nullptr; 53 | Window * m_statusBar = nullptr; 54 | HDRImageViewer * m_imageView = nullptr; 55 | ImageListPanel * m_imagesPanel = nullptr; 56 | 57 | Button * m_helpButton = nullptr; 58 | Button * m_sidePanelButton = nullptr; 59 | HelpWindow* m_helpWindow = nullptr; 60 | Label * m_zoomLabel = nullptr; 61 | Label * m_pixelInfoLabel = nullptr; 62 | 63 | VScrollPanel * m_sideScrollPanel = nullptr; 64 | Widget * m_sidePanelContents = nullptr; 65 | 66 | double m_guiAnimationStart; 67 | bool m_guiTimerRunning = false; 68 | enum EAnimationGoal : int 69 | { 70 | TOP_PANEL = 1 << 0, 71 | SIDE_PANEL = 1 << 1, 72 | BOTTOM_PANEL = 1 << 2, 73 | } m_animationGoal = EAnimationGoal(TOP_PANEL|SIDE_PANEL|BOTTOM_PANEL); 74 | 75 | MessageDialog * m_okToQuitDialog = nullptr; 76 | 77 | bool m_draggingSidePanel = false; 78 | 79 | std::shared_ptr console; 80 | }; 81 | -------------------------------------------------------------------------------- /src/FilmicToneCurve.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 | // This file is a modified version of Filmic Worlds' FilmicToneCurve class: 7 | // https://github.com/johnhable/fw-public 8 | // which was released into the public domain through the CC0 Universal license 9 | 10 | #pragma once 11 | 12 | #include "Common.h" 13 | 14 | class FilmicToneCurve 15 | { 16 | public: 17 | struct CurveParamsUser 18 | { 19 | float toeStrength = .25f; ///< as a ratio in [0,1] 20 | float toeLength = .25f; ///< as a ratio in [0,1] 21 | float shoulderStrength = 4.0f; ///< white point, in F stops 22 | float shoulderLength = 0.5f; ///< as a ratio in [0,1] 23 | float shoulderAngle = 0.5f; ///< as a ratio in [0,1] 24 | float gamma = 1.0f; 25 | }; 26 | 27 | struct CurveParamsDirect 28 | { 29 | CurveParamsDirect() 30 | { 31 | reset(); 32 | } 33 | 34 | void reset() 35 | { 36 | x0 = .25f; 37 | y0 = .25f; 38 | x1 = .75f; 39 | y1 = .75f; 40 | W = 1.0f; 41 | 42 | gamma = 1.0f; 43 | 44 | overshootX = 0.0f; 45 | overshootY = 0.0f; 46 | } 47 | 48 | float x0; 49 | float y0; 50 | float x1; 51 | float y1; 52 | float W; 53 | 54 | float overshootX; 55 | float overshootY; 56 | 57 | float gamma; 58 | }; 59 | 60 | struct CurveSegment 61 | { 62 | CurveSegment() 63 | { 64 | reset(); 65 | } 66 | 67 | void reset() 68 | { 69 | offsetX = 0.0f; 70 | offsetY = 0.0f; 71 | scaleX = 1.0f; // always 1 or -1 72 | lnA = 0.0f; 73 | B = 1.0f; 74 | } 75 | 76 | float eval(float x) const; 77 | float evalInv(float y) const; 78 | 79 | float offsetX; 80 | float offsetY; 81 | float scaleX; // always 1 or -1 82 | float scaleY; 83 | float lnA; 84 | float B; 85 | }; 86 | 87 | struct FullCurve 88 | { 89 | FullCurve() 90 | { 91 | reset(); 92 | } 93 | 94 | void reset() 95 | { 96 | W = 1.0f; 97 | invW = 1.0f; 98 | 99 | x0 = .25f; 100 | y0 = .25f; 101 | x1 = .75f; 102 | y1 = .75f; 103 | 104 | 105 | for (int i = 0; i < 3; i++) 106 | { 107 | m_segments[i].reset(); 108 | m_invSegments[i].reset(); 109 | } 110 | } 111 | 112 | float eval(float x) const; 113 | float evalInv(float x) const; 114 | 115 | float W; 116 | float invW; 117 | 118 | float x0; 119 | float x1; 120 | float y0; 121 | float y1; 122 | 123 | CurveSegment m_segments[3]; 124 | CurveSegment m_invSegments[3]; 125 | }; 126 | 127 | static void createCurve(FullCurve& dstCurve, const CurveParamsDirect& srcParams); 128 | static void calcDirectParamsFromUser(CurveParamsDirect& dstParams, const CurveParamsUser& srcParams); 129 | }; 130 | -------------------------------------------------------------------------------- /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 9 | #include 10 | #include 11 | #include "Progress.h" 12 | 13 | 14 | template 15 | class AsyncTask 16 | { 17 | public: 18 | #if FORCE_SERIAL 19 | static const auto policy = std::launch::deferred; 20 | #else 21 | static const auto policy = std::launch::async; 22 | #endif 23 | 24 | using TaskFunc = std::function; 25 | using NoProgressTaskFunc = std::function; 26 | 27 | /*! 28 | * Create an asyncronous task that can report back on its progress 29 | * @param compute The function to execute asyncrhonously 30 | */ 31 | AsyncTask(TaskFunc compute) 32 | : m_compute([compute](AtomicProgress & prog){T ret = compute(prog); prog.setDone(); return ret;}), m_progress(true) 33 | { 34 | 35 | } 36 | 37 | /*! 38 | * Create an asyncronous task without progress updates 39 | * @param compute The function to execute asyncrhonously 40 | */ 41 | AsyncTask(NoProgressTaskFunc compute) 42 | : m_compute([compute](AtomicProgress &){return compute();}), m_progress(false) 43 | { 44 | 45 | } 46 | 47 | /*! 48 | * Start the computation (if it hasn't already been started) 49 | */ 50 | void compute() 51 | { 52 | // start only if not done and not already started 53 | if (!m_future.valid() && !m_ready) 54 | m_future = std::async(policy, m_compute, std::ref(m_progress)); 55 | } 56 | 57 | /*! 58 | * Waits until the task has finished, and returns the result. 59 | * The tasks return value is cached, so get can be called multiple times. 60 | * 61 | * @return The result of the computation 62 | */ 63 | T & get() 64 | { 65 | if (m_ready) 66 | return m_value; 67 | 68 | m_value = m_future.valid() ? m_future.get() : m_compute(m_progress); 69 | 70 | m_ready = true; 71 | return m_value; 72 | } 73 | 74 | /*! 75 | * Query the progress of the task. 76 | * 77 | * @return The percentage done, ranging from 0.f to 100.f, 78 | * or -1 to indicate busy if the task doesn't report back progress 79 | */ 80 | float progress() const 81 | { 82 | return m_progress.progress(); 83 | } 84 | 85 | void setProgress(float p) 86 | { 87 | m_progress.resetProgress(p); 88 | } 89 | 90 | /*! 91 | * @return true if the computation has finished 92 | */ 93 | bool ready() const 94 | { 95 | if (m_ready) 96 | return true; 97 | 98 | if (!m_future.valid()) 99 | return false; 100 | 101 | auto status = m_future.wait_for(std::chrono::seconds(0)); 102 | 103 | // predent that the computation is ready for deferred execution since we will compute it on-demand in 104 | // get() anyway 105 | return (status == std::future_status::ready || status == std::future_status::deferred); 106 | } 107 | 108 | private: 109 | TaskFunc m_compute; 110 | std::future m_future; 111 | T m_value; 112 | AtomicProgress m_progress; 113 | bool m_ready = false; 114 | }; -------------------------------------------------------------------------------- /src/MultiGraph.h: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (C) Wojciech Jarosz . All rights reserved. 3 | // Use of this source code is governed by a BSD-style license that can 4 | // be found in the LICENSE.txt file. 5 | // 6 | 7 | #pragma once 8 | 9 | #include 10 | 11 | using namespace nanogui; 12 | 13 | /** 14 | * \class MultiGraph multigraph.h 15 | * 16 | * \brief A generalization of nanogui's graph widget which can plot multiple graphs on top of each other 17 | */ 18 | class MultiGraph : public Widget 19 | { 20 | public: 21 | MultiGraph(Widget *parent, const Color & fg = Color(255, 192, 0, 128), 22 | const VectorXf & v = VectorXf()); 23 | 24 | const Color &backgroundColor() const { return mBackgroundColor; } 25 | void setBackgroundColor(const Color &backgroundColor) { mBackgroundColor = backgroundColor; } 26 | 27 | const Color &textColor() const { return mTextColor; } 28 | void setTextColor(const Color &textColor) { mTextColor = textColor; } 29 | 30 | int numPlots() const {return mValues.size();} 31 | void addPlot(const Color & fg = Color(), const VectorXf & v = VectorXf()) {mValues.push_back(v); mForegroundColors.push_back(fg);} 32 | void popPlot() {mValues.pop_back(); mForegroundColors.pop_back();} 33 | 34 | bool well() const { return mInWell; } 35 | void setWell(bool b) { mInWell = b; } 36 | 37 | bool filled() const { return mFilled; } 38 | void setFilled(bool b) { mFilled = b; } 39 | 40 | const Color &foregroundColor(int plot = 0) const { return mForegroundColors[plot]; } 41 | void setForegroundColor(const Color &foregroundColor, int plot = 0) { mForegroundColors[plot] = foregroundColor; } 42 | 43 | const VectorXf &values(int plot = 0) const { return mValues[plot]; } 44 | VectorXf &values(int plot = 0) { return mValues[plot]; } 45 | void setValues(const VectorXf &values, int plot = 0) { mValues[plot] = values; } 46 | 47 | void setXTicks(const VectorXf & ticks, const std::vector & labels); 48 | void setYTicks(const VectorXf & ticks) { mYTicks = ticks; } 49 | void setLeftHeader(const std::string & s) { mLeftHeader = s; } 50 | void setCenterHeader(const std::string & s) { mCenterHeader = s; } 51 | void setRightHeader(const std::string & s) { mRightHeader = s; } 52 | 53 | std::function dragCallback() const { return mDragCallback; } 54 | void setDragCallback(const std::function &callback) { mDragCallback = callback; } 55 | 56 | virtual Vector2i preferredSize(NVGcontext *ctx) const override; 57 | virtual void draw(NVGcontext *ctx) override; 58 | virtual bool mouseDragEvent(const Vector2i &p, const Vector2i &rel, int button, int modifiers) override; 59 | virtual bool mouseButtonEvent(const Vector2i &p, int button, bool down, int modifiers) override; 60 | 61 | virtual void save(Serializer &s) const override; 62 | virtual bool load(Serializer &s) override; 63 | 64 | protected: 65 | Vector2f graphCoordinateAt(const Vector2f& position) const; 66 | float xPosition(float xfrac) const; 67 | float yPosition(float yfrac) const; 68 | 69 | Color mBackgroundColor, mTextColor; 70 | std::vector mForegroundColors; 71 | std::vector mValues; 72 | bool mFilled = true, mInWell = true; 73 | std::string mLeftHeader, mCenterHeader, mRightHeader; 74 | VectorXf mXTicks, mYTicks; 75 | std::vector mXTickLabels; 76 | 77 | std::function mDragCallback; 78 | 79 | public: 80 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW 81 | }; -------------------------------------------------------------------------------- /src/forced-random-dither.cpp: -------------------------------------------------------------------------------- 1 | /*! 2 | force-random-dither.cpp -- Generate a dither matrix using the force-random-dither method from: 3 | 4 | W. Purgathofer, R. F. Tobler and M. Geiler. 5 | "Forced random dithering: improved threshold matrices for ordered dithering" 6 | Image Processing, 1994. Proceedings. ICIP-94., IEEE International Conference, 7 | Austin, TX, 1994, pp. 1032-1035 vol.2. 8 | doi: 10.1109/ICIP.1994.413512 9 | */ 10 | // 11 | // Copyright (C) Wojciech Jarosz . All rights reserved. 12 | // Use of this source code is governed by a BSD-style license that can 13 | // be found in the LICENSE.txt file. 14 | // 15 | 16 | #include 17 | #include 18 | #include 19 | #include // std::random_shuffle 20 | #include // std::time 21 | #include // std::rand, std::srand 22 | 23 | using Eigen::MatrixXd; 24 | using Eigen::ArrayXXd; 25 | using Eigen::Vector2i; 26 | using namespace std; 27 | 28 | const int Sm = 128; 29 | const int Smk = Sm*Sm; 30 | 31 | 32 | double toroidalMinimumDistance(const Vector2i & a, const Vector2i & b) 33 | { 34 | int x0 = min(a.x(), b.x()); 35 | int x1 = max(a.x(), b.x()); 36 | int y0 = min(a.y(), b.y()); 37 | int y1 = max(a.y(), b.y()); 38 | double deltaX = min(x1-x0, x0+Sm-x1); 39 | double deltaY = min(y1-y0, y0+Sm-y1); 40 | return sqrt(deltaX*deltaX + deltaY*deltaY); 41 | } 42 | 43 | double force(double r) 44 | { 45 | return exp(-sqrt(2*r)); 46 | } 47 | 48 | int main(int, char **) 49 | { 50 | srand(unsigned ( std::time(0) ) ); 51 | 52 | vector freeLocations; 53 | ArrayXXd M = ArrayXXd::Zero(Sm,Sm); 54 | ArrayXXd forceField = ArrayXXd::Zero(Sm,Sm); 55 | 56 | // initialize free locations 57 | for (int y = 0; y < Sm; ++y) 58 | for (int x = 0; x < Sm; ++x) 59 | freeLocations.push_back(Vector2i(x,y)); 60 | 61 | 62 | for (int ditherValue = 0; ditherValue < Smk; ++ditherValue) 63 | { 64 | random_shuffle(freeLocations.begin(), freeLocations.end()); 65 | 66 | double minimum = 1e20f; 67 | Vector2i minimumLocation(0,0); 68 | 69 | // int halfP = freeLocations.size(); 70 | int halfP = min(max(1, (int)sqrt(freeLocations.size()*3/4)), (int)freeLocations.size()); 71 | // int halfP = min(10, (int)freeLocations.size()); 72 | for (int i = 0; i < halfP; ++i) 73 | { 74 | const Vector2i & location = freeLocations[i]; 75 | if (forceField(location.x(), location.y()) < minimum) 76 | { 77 | minimum = forceField(location.x(), location.y()); 78 | minimumLocation = location; 79 | } 80 | } 81 | 82 | Vector2i cell(0,0); 83 | for (cell.y() = 0; cell.y() < Sm; ++cell.y()) 84 | for (cell.x() = 0; cell.x() < Sm; ++cell.x()) 85 | { 86 | double r = toroidalMinimumDistance(cell, minimumLocation); 87 | forceField(cell.x(), cell.y()) += force(r); 88 | } 89 | 90 | freeLocations.erase(remove(freeLocations.begin(), freeLocations.end(), minimumLocation), freeLocations.end()); 91 | M(minimumLocation.x(), minimumLocation.y()) = ditherValue; 92 | 93 | // if (ditherValue % 16 == 0) 94 | // { 95 | // std::stringstream ss; 96 | // ss << ditherValue; 97 | // writeEXR("forceField-" + ss.str() + ".exr", forceField/(ditherValue+1)); 98 | // } 99 | } 100 | 101 | std::cout << M << std::endl; 102 | 103 | cout << "unsigned dither_matrix[" << Smk << "] = \n{\n "; 104 | printf("%5d", (int)M(0)); 105 | for (int i = 1; i < M.size(); ++i) 106 | { 107 | cout << ", "; 108 | if (i % Sm == 0) 109 | cout << endl << " "; 110 | printf("%5d", (int)M(i)); 111 | } 112 | 113 | cout << "\n};" << endl; 114 | 115 | return 0; 116 | } -------------------------------------------------------------------------------- /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) 23 | { 24 | return std::round(b * ScalingFactor); 25 | } 26 | 27 | static float fixed2float(Fixed f) 28 | { 29 | return float(f) / ScalingFactor; 30 | } 31 | 32 | std::atomic f; 33 | 34 | AtomicFixed() = default; 35 | 36 | explicit AtomicFixed(float d) : 37 | f(float2fixed(d)) 38 | { 39 | // empty 40 | } 41 | 42 | explicit operator float() const 43 | { 44 | return fixed2float(f); 45 | } 46 | 47 | Fixed operator=(float b) 48 | { 49 | return (f = float2fixed(b)); 50 | } 51 | 52 | Fixed operator+=(float b) 53 | { 54 | return (f += float2fixed(b)); 55 | } 56 | 57 | Fixed operator-=(float b) 58 | { 59 | return (f -= float2fixed(b)); 60 | } 61 | 62 | /// This operator is *NOT* atomic 63 | Fixed operator*=(float b) 64 | { 65 | return (f = Fixed(BigFixed(f) * BigFixed(float2fixed(b))) / ScalingFactor); 66 | } 67 | 68 | /// This operator is *NOT* atomic 69 | Fixed operator/=(float b) 70 | { 71 | return (f = Fixed((BigFixed(f) * ScalingFactor) / float2fixed(b))); 72 | } 73 | 74 | bool operator<(float b) const 75 | { 76 | return f < float2fixed(b); 77 | } 78 | 79 | bool operator<=(float b) const 80 | { 81 | return f <= float2fixed(b); 82 | } 83 | 84 | bool operator>(float b) const 85 | { 86 | return f > float2fixed(b); 87 | } 88 | 89 | bool operator>=(float b) const 90 | { 91 | return f >= float2fixed(b); 92 | } 93 | 94 | bool operator==(float b) const 95 | { 96 | return f == float2fixed(b); 97 | } 98 | 99 | bool operator!=(float b) const 100 | { 101 | return f != float2fixed(b); 102 | } 103 | }; 104 | 105 | 106 | using AtomicFixed16 = AtomicFixed; 107 | using AtomicFixed32 = AtomicFixed; 108 | 109 | 110 | /*! 111 | * Helper object to manage the progress display. 112 | * { 113 | * AtomicProgress p1(true); 114 | * p1.setNumSteps(10); 115 | * for (int i = 0; i < 10; ++i, ++p1) 116 | * { 117 | * // do something 118 | * } 119 | * } // end progress p1 120 | * 121 | */ 122 | class AtomicProgress 123 | { 124 | public: 125 | using AtomicPercent32 = AtomicFixed; 126 | 127 | explicit AtomicProgress(bool createState = false, float totalPercentage = 1.f); 128 | AtomicProgress(const AtomicProgress & parent, float percentageOfParent = 1.f); 129 | 130 | // access to the atomic internal storage 131 | void resetProgress(float p = 0.f); 132 | float progress() const; 133 | void setDone() {resetProgress(1.f);} 134 | void setBusy() {resetProgress(-1.f);} 135 | 136 | // access to the discrete stepping 137 | void setAvailablePercent(float percent); 138 | void setNumSteps(int numSteps); 139 | AtomicProgress& operator+=(int steps); 140 | AtomicProgress& operator++() {return ((*this)+=1);} 141 | 142 | private: 143 | int m_numSteps; 144 | float m_percentageOfParent, m_stepPercent; 145 | 146 | std::shared_ptr m_atomicState; ///< Atomic internal state of progress 147 | }; -------------------------------------------------------------------------------- /src/ImageButton.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 | // This file is adapted from tev: 7 | // This file was developed by Thomas Müller . 8 | // It is published under the BSD 3-Clause License within the LICENSE file. 9 | 10 | #pragma once 11 | 12 | #include "Common.h" 13 | 14 | #include 15 | 16 | #include 17 | 18 | class ImageButton : public nanogui::Widget 19 | { 20 | public: 21 | ImageButton(nanogui::Widget *parent, const std::string &caption); 22 | 23 | Eigen::Vector2i preferredSize(NVGcontext *ctx) const override; 24 | bool mouseButtonEvent(const Eigen::Vector2i &p, int button, bool down, int modifiers) override; 25 | void draw(NVGcontext *ctx) override; 26 | 27 | float progress() { return m_progress; } 28 | void setProgress(float progress) { m_progress = progress; } 29 | 30 | /// Set the button's text caption/filename 31 | void setCaption(const std::string &caption) { m_caption = caption; recomputeStringClipping(); } 32 | const std::string & caption() const { return m_caption; } 33 | void setImageId(size_t id) { m_id = id; } 34 | size_t imageId() const { return m_id; } 35 | void setIsModified(bool b) { m_isModified = b; } 36 | bool isModified() const { return m_isModified; } 37 | bool isSelected() const { return m_isSelected; } 38 | void setIsSelected(bool isSelected) { m_isSelected = isSelected; } 39 | bool isReference() const { return m_isReference; } 40 | void setIsReference(bool isReference) { m_isReference = isReference; } 41 | 42 | 43 | std::string highlighted() const; 44 | void setHighlightRange(size_t begin, size_t end); 45 | 46 | void recomputeStringClipping(); 47 | 48 | void setSelectedCallback(const std::function & callback) 49 | { 50 | m_selectedCallback = callback; 51 | } 52 | 53 | void setReferenceCallback(const std::function & callback) 54 | { 55 | m_referenceCallback = callback; 56 | } 57 | 58 | 59 | void swapWith(ImageButton & other) 60 | { 61 | std::swap(m_caption, other.m_caption); 62 | std::swap(m_isModified, other.m_isModified); 63 | // std::swap(m_isSelected, other.m_isSelected); 64 | // std::swap(m_isReference, other.m_isReference); 65 | // std::swap(m_selectedCallback, other.m_selectedCallback); 66 | // std::swap(m_referenceCallback, other.m_referenceCallback); 67 | // std::swap(m_id, other.m_id); 68 | std::swap(m_progress, other.m_progress); 69 | std::swap(m_highlightBegin, other.m_highlightBegin); 70 | std::swap(m_highlightEnd, other.m_highlightEnd); 71 | std::swap(mTooltip, other.mTooltip); 72 | 73 | // swapping may need to recompute trimming 74 | m_cutoff = 0; 75 | m_sizeForWhichCutoffWasComputed = Eigen::Vector2i::Constant(0); 76 | other.m_cutoff = 0; 77 | other.m_sizeForWhichCutoffWasComputed = Eigen::Vector2i::Constant(0); 78 | } 79 | 80 | private: 81 | std::string m_caption; 82 | 83 | bool m_isModified = false; 84 | bool m_isSelected = false; 85 | bool m_isReference = false; 86 | std::function m_selectedCallback; 87 | std::function m_referenceCallback; 88 | 89 | size_t m_id = 0; 90 | size_t m_cutoff = 0; 91 | Eigen::Vector2i m_sizeForWhichCutoffWasComputed = Eigen::Vector2i::Constant(0); 92 | 93 | size_t m_highlightBegin = 0; 94 | size_t m_highlightEnd = 0; 95 | 96 | float m_progress = -1.f; 97 | 98 | public: 99 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW 100 | }; 101 | -------------------------------------------------------------------------------- /src/Colorspace.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 | #include 12 | 13 | /*! 14 | * @brief Generic color space conversion 15 | * 16 | * Converts from a source color space \a src to a destination color space \a dst, 17 | * each specified using the \a EColorSpace enumeriation 18 | * 19 | * @param[in] dst Destination color space 20 | * @param[out] a First component of the destination color 21 | * @param[out] b Second component of the destination color 22 | * @param[out] c Third component of the destination color 23 | * @param[in] src Source color space 24 | * @param[in] A First component of the source color 25 | * @param[in] B Second component of the source color 26 | * @param[in] C Third component of the source color 27 | */ 28 | void convertColorSpace(EColorSpace dst, float *a, float *b, float *c, EColorSpace src, float A, float B, float C); 29 | 30 | // to/from linear to sRGB and AdobeRGB 31 | float SRGBToLinear(float a); 32 | void SRGBToLinear(float * r, float * g, float * b); 33 | Color3 SRGBToLinear(const Color3 &c); 34 | Color4 SRGBToLinear(const Color4 &c); 35 | float LinearToSRGB(float a); 36 | void LinearToSRGB(float * r, float * g, float * b); 37 | Color3 LinearToSRGB(const Color3 &c); 38 | Color4 LinearToSRGB(const Color4 &c); 39 | float AdobeRGBToLinear(float a); 40 | void AdobeRGBToLinear(float * r, float * g, float * b); 41 | Color3 AdobeRGBToLinear(const Color3 &c); 42 | Color4 AdobeRGBToLinear(const Color4 &c); 43 | float LinearToAdobeRGB(float a); 44 | void LinearToAdobeRGB(float * r, float * g, float * b); 45 | Color3 LinearToAdobeRGB(const Color3 &c); 46 | Color4 LinearToAdobeRGB(const Color4 &c); 47 | 48 | // to and from XYZ 49 | void XYZToLinearSRGB(float *R, float *G, float *B, float X, float Y, float z); 50 | void LinearSRGBToXYZ(float *X, float *Y, float *Z, float R, float G, float B); 51 | void XYZToLinearAdobeRGB(float *R, float *G, float *B, float X, float Y, float z); 52 | void LinearAdobeRGBToXYZ(float *X, float *Y, float *Z, float R, float G, float B); 53 | void XYZToLab(float *L, float *a, float *b, float X, float Y, float Z); 54 | void LabToXYZ(float *X, float *Y, float *Z, float L, float a, float b); 55 | void XYZToLuv(float *L, float *u, float *v, float X, float Y, float Z); 56 | void LuvToXYZ(float *X, float *Y, float *Z, float L, float u, float v); 57 | void XYZToxy(float *x, float *y, float X, float Y, float Z); 58 | void xyYToXZ(float *X, float *Z, float x, float y, float Y); 59 | void XYZToHSL(float *H, float *L, float *S, float X, float Y, float Z); 60 | void HSLToXYZ(float *X, float *Y, float *Z, float H, float L, float S); 61 | void XYZToHSV(float *H, float *S, float *V, float X, float Y, float Z); 62 | void HSVToXYZ(float *X, float *Y, float *Z, float H, float S, float V); 63 | 64 | //! Normalize the L,a,b values to each fall within the range [0,1] 65 | void normalizeLab(float * L, float * a, float * b); 66 | //! Take normalized L,a,b values and undo the normalization back to the original range 67 | void unnormalizeLab(float * L, float * a, float * b); 68 | 69 | // HLS and HSV are more naturally defined as transformations to/from RGB, so 70 | // define those explicitly 71 | void RGBToHSL(float *H, float *L, float *S, float R, float G, float B); 72 | void HSLToRGB(float *R, float *G, float *B, float H, float L, float S); 73 | void RGBToHSV(float *H, float *S, float *V, float R, float G, float B); 74 | void HSVToRGB(float *R, float *G, float *B, float H, float S, float V); 75 | void HSIAdjust(float *R, float *G, float *B, float h, float s, float i); 76 | void HSLAdjust(float *R, float *G, float *B, float h, float s, float l); 77 | void SatAdjust(float *R, float *G, float *B, float s); 78 | 79 | 80 | 81 | const std::vector & colorSpaceNames(); -------------------------------------------------------------------------------- /src/CommandHistory.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 // for uint32_t 10 | #include // for Vector2i, Matrix4f, Vector3f 11 | #include // for vector, allocator 12 | #include "HDRImage.h" // for HDRImage 13 | #include "Fwd.h" // for HDRImage 14 | 15 | //! Generic image manipulation undo class 16 | class ImageCommandUndo 17 | { 18 | public: 19 | virtual ~ImageCommandUndo() = default; 20 | 21 | virtual void undo(std::shared_ptr & img) = 0; 22 | virtual void redo(std::shared_ptr & img) = 0; 23 | }; 24 | 25 | using UndoPtr = std::shared_ptr; 26 | using ImageCommandResult = std::pair, UndoPtr>; 27 | 28 | using ImageCommand = std::function &)>; 29 | using ImageCommandWithProgress = std::function &, AtomicProgress &)>; 30 | 31 | 32 | //! Brute-force undo: Saves the entire image data so that we can copy it back 33 | class FullImageUndo : public ImageCommandUndo 34 | { 35 | public: 36 | explicit FullImageUndo(const HDRImage & img) : m_undoImage(std::make_shared(img)) {} 37 | ~FullImageUndo() override = default; 38 | 39 | void undo(std::shared_ptr & img) override {img.swap(m_undoImage);} 40 | void redo(std::shared_ptr & img) override {undo(img);} 41 | 42 | const std::shared_ptr image() const {return m_undoImage;} 43 | 44 | private: 45 | std::shared_ptr m_undoImage; 46 | }; 47 | 48 | //! Specify the undo and redo commands using lambda expressions 49 | class LambdaUndo : public ImageCommandUndo 50 | { 51 | public: 52 | explicit LambdaUndo(const std::function & img)> & undoCmd, 53 | const std::function & img)> & redoCmd = nullptr) : 54 | m_undo(undoCmd), m_redo(redoCmd ? redoCmd : undoCmd) {} 55 | ~LambdaUndo() override = default; 56 | 57 | void undo(std::shared_ptr & img) override {m_undo(img);} 58 | void redo(std::shared_ptr & img) override {m_redo(img);} 59 | 60 | private: 61 | std::function & img)> m_undo, m_redo; 62 | }; 63 | 64 | //! Stores and manages an undo history list for image modifications 65 | class CommandHistory 66 | { 67 | public: 68 | CommandHistory() : 69 | m_currentState(0), m_savedState(0) 70 | { 71 | // empty 72 | } 73 | 74 | ~CommandHistory() 75 | { 76 | // empty 77 | } 78 | 79 | bool isModified() const {return m_currentState != m_savedState;} 80 | void markSaved() {m_savedState = m_currentState;} 81 | 82 | int currentState() const {return m_currentState;} 83 | int savedState() const {return m_savedState;} 84 | int size() const {return m_history.size();} 85 | bool hasUndo() const {return m_currentState > 0;} 86 | bool hasRedo() const {return m_currentState < size();} 87 | 88 | void addCommand(UndoPtr cmd) 89 | { 90 | // deletes all history newer than the current state 91 | m_history.resize(m_currentState); 92 | 93 | // add the new command and increment state 94 | m_history.push_back(std::move(cmd)); 95 | m_currentState++; 96 | } 97 | 98 | bool undo(std::shared_ptr & img) 99 | { 100 | // check if there is anything to undo 101 | if (!hasUndo() || m_currentState > size()) 102 | return false; 103 | 104 | m_history[--m_currentState]->undo(img); 105 | return true; 106 | } 107 | bool redo(std::shared_ptr & img) 108 | { 109 | // check if there is anything to redo 110 | if (!hasRedo() || m_currentState < 0) 111 | return false; 112 | 113 | m_history[m_currentState++]->redo(img); 114 | return true; 115 | } 116 | 117 | private: 118 | std::vector m_history; 119 | 120 | // it is best to think of this state as pointing in between the entries in the m_history vector 121 | // it can range from [0,size()] 122 | // m_currentState == 0 indicates that there is nothing to undo 123 | // m_currentState == size() indicates that there is nothing to redo 124 | int m_currentState; 125 | int m_savedState; 126 | }; -------------------------------------------------------------------------------- /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 | 10 | using namespace std; 11 | 12 | string getExtension(const string& filename) 13 | { 14 | if (filename.find_last_of(".") != string::npos) 15 | return filename.substr(filename.find_last_of(".")+1); 16 | return ""; 17 | } 18 | 19 | string getBasename(const string& filename) 20 | { 21 | auto lastSlash = filename.find_last_of("/\\"); 22 | auto lastDot = filename.find_last_of("."); 23 | if (lastSlash == std::string::npos && lastDot == std::string::npos) 24 | return filename; 25 | 26 | auto start = (lastSlash != string::npos) ? lastSlash + 1 : 0; 27 | auto length = (lastDot != string::npos) ? lastDot-start : filename.size()-start; 28 | return filename.substr(start, length); 29 | } 30 | 31 | 32 | const vector & channelNames() 33 | { 34 | static const vector names = 35 | { 36 | "RGB", 37 | "Red", 38 | "Green", 39 | "Blue", 40 | "Luminance", 41 | "CIE L*", 42 | "CIE a*", 43 | "CIE b*", 44 | "CIE chromaticity", 45 | "False color", 46 | "Negative-positive" 47 | }; 48 | return names; 49 | } 50 | 51 | const vector & blendModeNames() 52 | { 53 | static const vector names = 54 | { 55 | "Normal", 56 | "Multiply", 57 | "Divide", 58 | "Add", 59 | "Average", 60 | "Subtract", 61 | "Difference", 62 | "Relative difference", 63 | }; 64 | return names; 65 | } 66 | 67 | string channelToString(EChannel channel) 68 | { 69 | return channelNames()[channel]; 70 | } 71 | 72 | string blendModeToString(EBlendMode mode) 73 | { 74 | return blendModeNames()[mode]; 75 | } 76 | 77 | 78 | // The following functions are adapted from tev: 79 | // This file was developed by Thomas Müller . 80 | // It is published under the BSD 3-Clause License within the LICENSE file. 81 | 82 | vector split(string text, const string& delim) 83 | { 84 | vector result; 85 | while (true) 86 | { 87 | size_t begin = text.find_last_of(delim); 88 | if (begin == string::npos) 89 | { 90 | result.emplace_back(text); 91 | return result; 92 | } 93 | else 94 | { 95 | result.emplace_back(text.substr(begin + 1)); 96 | text.resize(begin); 97 | } 98 | } 99 | 100 | return result; 101 | } 102 | 103 | string toLower(string str) 104 | { 105 | transform(begin(str), end(str), begin(str), [](unsigned char c) { return (char)tolower(c); }); 106 | return str; 107 | } 108 | 109 | string toUpper(string str) 110 | { 111 | transform(begin(str), end(str), begin(str), [](unsigned char c) { return (char)toupper(c); }); 112 | return str; 113 | } 114 | 115 | bool matches(string text, string filter, bool isRegex) 116 | { 117 | auto matchesFuzzy = [](string text, string filter) 118 | { 119 | if (filter.empty()) 120 | return true; 121 | 122 | // Perform matching on lowercase strings 123 | text = toLower(text); 124 | filter = toLower(filter); 125 | 126 | auto words = split(filter, ", "); 127 | // We don't want people entering multiple spaces in a row to match everything. 128 | words.erase(remove(begin(words), end(words), ""), end(words)); 129 | 130 | if (words.empty()) 131 | return true; 132 | 133 | // Match every word of the filter separately. 134 | for (const auto& word : words) 135 | if (text.find(word) != string::npos) 136 | return true; 137 | 138 | return false; 139 | }; 140 | 141 | auto matchesRegex = [](string text, string filter) 142 | { 143 | if (filter.empty()) 144 | return true; 145 | 146 | try 147 | { 148 | regex searchRegex{filter, std::regex_constants::ECMAScript | std::regex_constants::icase}; 149 | return regex_search(text, searchRegex); 150 | } 151 | catch (const regex_error&) 152 | { 153 | return false; 154 | } 155 | }; 156 | 157 | return isRegex ? matchesRegex(text, filter) : matchesFuzzy(text, filter); 158 | } -------------------------------------------------------------------------------- /src/ImageListPanel.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 "Common.h" 12 | #include "GLImage.h" 13 | #include "Fwd.h" 14 | 15 | using namespace nanogui; 16 | 17 | class ImageListPanel : public Widget 18 | { 19 | public: 20 | ImageListPanel(Widget *parent, HDRViewScreen * screen, HDRImageViewer * imgViewer); 21 | 22 | void draw(NVGcontext *ctx) override; 23 | 24 | void repopulateImageList(); 25 | 26 | // Const access to the loaded images. Modification only possible via modifyImage, undo, redo 27 | int numImages() const {return int(m_images.size());} 28 | int currentImageIndex() const {return m_current;} 29 | int referenceImageIndex() const {return m_reference;} 30 | ConstImagePtr currentImage() const {return image(m_current);} 31 | ImagePtr currentImage() {return image(m_current);} 32 | ConstImagePtr referenceImage() const {return image(m_reference);} 33 | ImagePtr referenceImage() {return image(m_reference);} 34 | ConstImagePtr image(int index) const; 35 | ImagePtr image(int index); 36 | 37 | bool setCurrentImageIndex(int newIndex, bool forceCallback = false); 38 | bool setReferenceImageIndex(int newIndex); 39 | bool swapCurrentSelectedWithPrevious() {printf("current: %d; previous: %d\n", m_current, m_previous);return isValid(m_previous) ? setCurrentImageIndex(m_previous) : false;} 40 | bool swapImages(int index1, int index2); 41 | bool sendImageBackward(); 42 | bool bringImageForward(); 43 | 44 | // Loading, saving, closing, and rearranging the images in the image stack 45 | void loadImages(const std::vector & filenames); 46 | bool saveImage(const std::string & filename, float exposure = 0.f, float gamma = 2.2f, 47 | bool sRGB = true, bool dither = true); 48 | bool closeImage(); 49 | void closeAllImages(); 50 | 51 | // Modify the image data 52 | void modifyImage(const ImageCommand & command); 53 | void modifyImage(const ImageCommandWithProgress & command); 54 | void undo(); 55 | void redo(); 56 | 57 | // 58 | void runRequestedCallbacks(); 59 | 60 | 61 | void requestButtonsUpdate(); 62 | void requestHistogramUpdate(bool force = false); 63 | 64 | EBlendMode blendMode() const; 65 | void setBlendMode(EBlendMode mode); 66 | 67 | EChannel channel() const; 68 | void setChannel(EChannel channel); 69 | 70 | bool nthImageIsVisible(int n) const; 71 | int nextVisibleImage(int index, EDirection direction) const; 72 | int nthVisibleImageIndex(int n) const; 73 | 74 | bool useRegex() const; 75 | void setUseRegex(bool value); 76 | 77 | bool setFilter(const std::string& filter); 78 | std::string filter() const; 79 | void focusFilter(); 80 | 81 | 82 | private: 83 | void updateButtons(); 84 | void enableDisableButtons(); 85 | void updateHistogram(); 86 | void updateFilter(); 87 | bool isValid(int index) const {return index >= 0 && index < numImages();} 88 | 89 | std::vector m_images; ///< The loaded images 90 | int m_current = -1; ///< The currently selected image 91 | int m_reference = -1; ///< The currently selected reference image 92 | 93 | int m_previous = -1; ///< The previously selected image 94 | 95 | std::atomic m_imageModifyDoneRequested; 96 | 97 | // various callback functions 98 | std::function m_imageModifyDoneCallback; 99 | std::function m_numImagesCallback; 100 | std::function m_currentImageCallback; 101 | std::function m_referenceImageCallback; 102 | 103 | 104 | HDRViewScreen * m_screen = nullptr; 105 | HDRImageViewer * m_imageViewer = nullptr; 106 | Button * m_saveButton = nullptr; 107 | Button * m_closeButton = nullptr; 108 | Button * m_bringForwardButton = nullptr; 109 | Button * m_sendBackwardButton = nullptr; 110 | TextBox * m_filter = nullptr; 111 | Button* m_eraseButton = nullptr; 112 | Button* m_regexButton = nullptr; 113 | Button * m_useShortButton = nullptr; 114 | Widget * m_imageListWidget = nullptr; 115 | ComboBox * m_blendModes = nullptr; 116 | ComboBox * m_channels = nullptr; 117 | std::vector m_imageButtons; 118 | 119 | ComboBox * m_xAxisScale = nullptr, 120 | * m_yAxisScale = nullptr; 121 | MultiGraph * m_graph = nullptr; 122 | bool m_histogramDirty = false; 123 | bool m_histogramUpdateRequested = false; 124 | bool m_updateFilterRequested = true; 125 | bool m_buttonsUpdateRequested = true; 126 | double m_histogramRequestTime; 127 | 128 | public: 129 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW 130 | }; -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HDRView 2 | 3 | Master status: 4 | [![Ubuntu build status](https://semaphoreci.com/api/v1/wjarosz/hdrview/branches/master/shields_badge.svg)](https://semaphoreci.com/wjarosz/hdrview) 5 | [![Windows build status](https://ci.appveyor.com/api/projects/status/github/cdcseacave/hdrview?branch=master&svg=true)](https://ci.appveyor.com/project/cdcseacave/hdrview/branch/master) 6 | 7 | Develop status: 8 | [![Ubuntu build status](https://semaphoreci.com/api/v1/wjarosz/hdrview/branches/develop/shields_badge.svg)](https://semaphoreci.com/wjarosz/hdrview) 9 | [![Windows build status](https://ci.appveyor.com/api/projects/status/github/cdcseacave/hdrview?branch=develop&svg=true)](https://ci.appveyor.com/project/cdcseacave/hdrview/branch/develop) 10 | 11 | 12 | HDRView is a simple research-oriented depth-map & high-dynamic range image viewer with an emphasis on examining and comparing images, and including minimalistic tonemapping capabilities. HDRView currently supports reading EXR, PNG, TGA, BMP, HDR, JPG, GIF, PNM, PFM, and PSD images and writing EXR, HDR, PNG, TGA, PPM, PFM, and BMP images. 13 | 14 | ## Example screenshots 15 | HDRView supports loading several depth-map formats (PFM, PNG 1-channel-16bit, EXR, NPY) and convert them to RGB images for visualization. 16 | ![Screenshot](resources/screenshot0.png "Screenshot0") 17 | HDRView supports loading several images and provides exposure and gamma/sRGB tone mapping control with high-quality dithering of HDR images. 18 | ![Screenshot](resources/screenshot1.png "Screenshot1") 19 | When sufficiently zoomed in, HDRView can overlay the pixel grid and numeric color values on each pixel to facilitate inspection. 20 | ![Screenshot](resources/screenshot2.png "Screenshot2") 21 | Displaying HDR images naively on a 24 bit display leads to visible banding in smooth gradients. 22 | ![Screenshot](resources/screenshot3.png "Screenshot3") 23 | HDRView supports high-quality dithering (both when viewing and when saving to an LDR file) to reduce these artifacts. 24 | ![Screenshot](resources/screenshot4.png "Screenshot4") 25 | 26 | ## Obtaining HDRView 27 | 28 | If you are running a recent version of macOS, you can download the pre-built binary installer DMG from the [releases page](https://bitbucket.org/wkjarosz/hdrview/downloads). For other platforms, you will need to build HDRView from source for now. 29 | 30 | ## Compiling 31 | 32 | Compiling from scratch requires CMake and a recent version of the XCode build tools on macOS, Visual Studio 2015 on Windows, and GCC on Linux. 33 | 34 | ### Linux and macOS 35 | 36 | On Linux and macOS, compiling should be as simple as 37 | 38 | git clone --recursive https://bitbucket.org/wkjarosz/hdrview.git 39 | cd hdrview 40 | mkdir build 41 | cd build 42 | cmake-gui ../ 43 | make -j 4 44 | 45 | ### Windows 46 | 47 | On Windows, a few extra steps are needed. 48 | 49 | Since MSVC's regex implementation is buggy, you first need to have the Boost regex library installed. You can find binary installers for Windows on the [Boost website](http://www.boost.org/). You need at least Boost version 1.53. Once installed, you can run: 50 | 51 | git clone --recursive https://bitbucket.org/wkjarosz/hdrview.git 52 | cd hdrview 53 | mkdir build 54 | cd build 55 | cmake ../ 56 | -G"Visual Studio 15 2017 Win64" 57 | -DBOOST_ROOT="C:\where_you_installed_boost" 58 | -DUSE_BOOST_REGEX=true 59 | 60 | You can also do this through ``cmake-gui`` if you prefer. Click ``Add Entry`` and define ``BOOST_ROOT`` to the directory where you installed Boost (by default something like ``C:\local\boost_1_65_0``). Run ``Configure`` and select your version of Visual C++ and 64bit. After configure finishes, search for ``USE_BOOST_REGEX`` and check it. Run ``Configure`` again, and then click ``Generate``. 61 | 62 | Open the generated file ``HDRView.sln`` and proceed building as usual from within Visual Studio. 63 | 64 | 65 | ## Installing on macOS 66 | 67 | This should be as easy as ``make install``. On macOS this will copy the application bundle into /Applications and create the symlink ``hdrview`` in ``/usr/local/bin`` so you can launch HDRView from the terminal. 68 | 69 | ## HDRView usage 70 | 71 | Run ``./hdrview --help`` to see the command-line options, or run ``./hdrview `` and hit the ``h`` button to see a list of keyboard shortcuts in the application. 72 | 73 | ## hdrbatch usage 74 | 75 | There is also a separate executable ``hdrbatch`` intended for batch processing/converting images. Run ``./hdrbatch --help`` to see the command-line options. 76 | 77 | ## License 78 | 79 | Copyright (c) Wojciech Jarosz 80 | 81 | 3-clause BSD. For details, see the ``LICENSE.txt`` file. 82 | 83 | HDRView depends on the following libraries (which are included explicitly or as git submodules): 84 | 85 | * Wenzel Jakob's [NanoGUI](https://github.com/wjakob/nanogui) library, which is licensed under a BSD-style license. 86 | * ILM's [OpenEXR](http://www.openexr.com) library, which is licensed under a modified BSD license. 87 | * Some [stb](https://github.com/nothings/stb) libraries, developed by Sean Barrett and released into the public domain. 88 | * The [tinydir](https://github.com/cxong/tinydir/) library, which is licensed under a simplified BSD. 89 | * The [docopt.cpp](https://github.com/docopt/docopt.cpp) library, which is dual-licensed under MIT and Boost licenses. 90 | * Gabi Melman's [spdlog](https://github.com/gabime/spdlog) library, which is licensed under the MIT license. 91 | * syoyo's [tinydngloader](https://github.com/syoyo/tinydngloader) library, which is licensed under the MIT license. -------------------------------------------------------------------------------- /src/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 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | using namespace std; 16 | 17 | 18 | namespace 19 | { 20 | 21 | float reinterpretAsHostEndian(float f, bool bigEndian) 22 | { 23 | static_assert(sizeof(float) == sizeof(unsigned int), "Sizes must match"); 24 | 25 | const auto* uchar = (const unsigned char *) &f; 26 | uint32_t i; 27 | if (bigEndian) 28 | i = (uchar[3]<<0) | (uchar[2]<<8) | (uchar[1]<<16) | (uchar[0]<<24); 29 | else 30 | i = (uchar[0]<<0) | (uchar[1]<<8) | (uchar[2]<<16) | (uchar[3]<<24); 31 | 32 | float ret; 33 | memcpy(&ret, &i, sizeof(float)); 34 | return ret; 35 | } 36 | 37 | } // end namespace 38 | 39 | bool isPFMImage(const char *filename) noexcept 40 | { 41 | FILE *f = nullptr; 42 | int numInputsRead = 0; 43 | 44 | try 45 | { 46 | f = fopen(filename, "rb"); 47 | 48 | if (!f) 49 | throw runtime_error("loadPFMImage: Error opening"); 50 | 51 | char buffer[1024]; 52 | numInputsRead = fscanf(f, "%2s\n", buffer); 53 | if (numInputsRead != 1) 54 | throw runtime_error("loadPFMImage: Could not read number of channels in header"); 55 | 56 | if (!(strcmp(buffer, "Pf") == 0 || strcmp(buffer, "PF") == 0)) 57 | throw runtime_error("loadPFMImage: Cannot deduce number of channels from header"); 58 | 59 | int width, height; 60 | numInputsRead = fscanf(f, "%d%d", &width, &height); 61 | if (numInputsRead != 2 || width <= 0 || height <= 0) 62 | throw runtime_error("loadPFMImage: Invalid image width or height"); 63 | 64 | float scale; 65 | numInputsRead = fscanf(f, "%f", &scale); 66 | if (numInputsRead != 1) 67 | throw runtime_error("loadPFMImage: Invalid file endianness. Big-Endian files not supported"); 68 | 69 | fclose(f); 70 | return true; 71 | } 72 | catch (const exception & e) 73 | { 74 | fclose(f); 75 | return false; 76 | } 77 | } 78 | 79 | float * loadPFMImage(const char *filename, int *width, int *height, int *numChannels) 80 | { 81 | float * data = nullptr; 82 | FILE *f = nullptr; 83 | int numInputsRead = 0; 84 | 85 | try 86 | { 87 | f = fopen(filename, "rb"); 88 | 89 | if (!f) 90 | throw runtime_error("loadPFMImage: Error opening"); 91 | 92 | char buffer[1024]; 93 | numInputsRead = fscanf(f, "%2s\n", buffer); 94 | if (numInputsRead != 1) 95 | throw runtime_error("loadPFMImage: Could not read number of channels in header"); 96 | 97 | if (strcmp(buffer, "Pf") == 0) 98 | *numChannels = 1; 99 | else if (strcmp(buffer, "PF") == 0) 100 | *numChannels = 3; 101 | else 102 | throw runtime_error("loadPFMImage: Cannot deduce number of channels from header"); 103 | 104 | 105 | numInputsRead = fscanf(f, "%d%d", width, height); 106 | if (numInputsRead != 2 || *width <= 0 || *height <= 0) 107 | throw runtime_error("loadPFMImage: Invalid image width or height"); 108 | 109 | float scale; 110 | numInputsRead = fscanf(f, "%f", &scale); 111 | if (numInputsRead != 1) 112 | throw runtime_error("loadPFMImage: Invalid file endianness. Big-Endian files not supported"); 113 | 114 | bool bigEndian = scale > 0.0f; 115 | 116 | data = new float[(*width) * (*height) * 3]; 117 | 118 | if (fread(data, 1, 1, f) != 1) 119 | throw runtime_error("loadPFMImage: Unknown error"); 120 | 121 | size_t numFloats = static_cast((*width) * (*height) * (*numChannels)); 122 | if (fread(data, sizeof(float), numFloats, f) != numFloats) 123 | throw runtime_error("loadPFMImage: Could not read all pixel data"); 124 | 125 | // multiply data by scale factor 126 | scale = fabsf(scale); 127 | for (size_t i = 0; i < numFloats; ++i) 128 | data[i] = scale*reinterpretAsHostEndian(data[i], bigEndian); 129 | 130 | fclose(f); 131 | return data; 132 | } 133 | catch (const runtime_error & e) 134 | { 135 | fclose(f); 136 | delete [] data; 137 | throw runtime_error(string(e.what()) + " in file '" + filename + "'"); 138 | } 139 | } 140 | 141 | bool writePFMImage(const char *filename, int width, int height, int numChannels, const float *data) 142 | { 143 | FILE *f = fopen(filename, "wb"); 144 | 145 | if (!f) 146 | { 147 | cerr << "writePFMImage: Error opening file '" << filename << "'" << endl; 148 | return false; 149 | } 150 | 151 | fprintf(f, numChannels == 1 ? "Pf\n" : "PF\n"); 152 | fprintf(f, "%d %d\n", width, height); 153 | 154 | // determine system endianness 155 | bool littleEndian = false; 156 | { 157 | int n = 1; 158 | // little endian if true 159 | if (*(char *)&n == 1) 160 | littleEndian = true; 161 | } 162 | 163 | fprintf(f, littleEndian ? "-1.0000000\n" : "1.0000000\n"); 164 | 165 | if (numChannels == 3 || numChannels == 1) 166 | { 167 | fwrite(&data[0], width * height * sizeof(float) * numChannels, 1, f); 168 | } 169 | else if (numChannels == 4) 170 | { 171 | for (int i = 0; i < width * height * 4; i += 4) 172 | fwrite(&data[i], sizeof(float) * 3, 1, f); 173 | } 174 | else 175 | { 176 | fclose(f); 177 | cerr << "writePFMImage: Unsupported number of channels " 178 | << numChannels << " when writing file '" << filename << "'" << endl; 179 | return false; 180 | } 181 | 182 | fclose(f); 183 | return true; 184 | } 185 | -------------------------------------------------------------------------------- /src/HDRView.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 8 | #include 9 | #include 10 | #include "HDRViewer.h" 11 | #include 12 | #include 13 | 14 | using namespace std; 15 | namespace spd = spdlog; 16 | 17 | // Force usage of discrete GPU on laptops 18 | NANOGUI_FORCE_DISCRETE_GPU(); 19 | 20 | static const char USAGE[] = 21 | R"(HDRView. Copyright (c) Wojciech Jarosz. 22 | 23 | HDRView is a simple research-oriented tool for examining, 24 | comparing, and converting high-dynamic range images. HDRView 25 | is freely available under a 3-clause BSD license. 26 | 27 | Usage: 28 | hdrview [options FILE...] 29 | hdrview -h | --help | --version 30 | 31 | Options: 32 | -e E, --exposure=E Desired power of 2 EV or exposure value 33 | (gain = 2^exposure) [default: 0]. 34 | -g G, --gamma=G Desired gamma value for exposure+gamma tonemapping. 35 | An sRGB curve is used if gamma is not specified. 36 | -d, --no-dither Disable dithering. 37 | -v T, --verbose=T Set verbosity threshold with lower values meaning 38 | more verbose and higher values removing low-priority 39 | messages. 40 | T : (0 | 1 | 2 | 3 | 4 | 5 | 6) [default: 1]. 41 | All messages with severity >= T are displayed, where 42 | the severities are: 43 | trace = 0 44 | debug = 1 45 | info = 2 46 | warn = 3 47 | err = 4 48 | critical = 5 49 | off = 6 50 | -h, --help Display this message. 51 | --version Show the version. 52 | )"; 53 | 54 | 55 | int main(int argc, char **argv) 56 | { 57 | vector argVector = { argv + 1, argv + argc }; 58 | map docargs; 59 | int verbosity = 0; 60 | float gamma = 2.2f, exposure = 0.f; 61 | bool dither = true, sRGB = true; 62 | 63 | vector inFiles; 64 | 65 | try 66 | { 67 | #if defined(__APPLE__) 68 | bool launched_from_finder = false; 69 | // check whether -psn is set, and remove it from the arguments 70 | for (vector::iterator i = argVector.begin(); i != argVector.end(); ++i) 71 | { 72 | if (strncmp("-psn", i->c_str(), 4) == 0) 73 | { 74 | launched_from_finder = true; 75 | argVector.erase(i); 76 | break; 77 | } 78 | } 79 | #endif 80 | 81 | // Console logger with color 82 | auto console = spd::stdout_color_mt("console"); 83 | spd::set_pattern("[%l] %v"); 84 | spd::set_level(spd::level::level_enum(2)); 85 | 86 | #ifdef _MSC_VER 87 | if (argVector.size() == 1) 88 | inFiles.emplace_back(argVector[0]); 89 | #else 90 | docargs = docopt::docopt(USAGE, argVector, 91 | true, // show help if requested 92 | "HDRView " HDRVIEW_VERSION); // version string 93 | 94 | verbosity = docargs["--verbose"].asLong(); 95 | 96 | if (verbosity < spd::level::trace || verbosity > spd::level::off) 97 | { 98 | console->error("Invalid verbosity threshold. Setting to default \"2\""); 99 | verbosity = 2; 100 | } 101 | 102 | spd::set_level(spd::level::level_enum(verbosity)); 103 | 104 | console->info("Welcome to HDRView!"); 105 | console->info("Verbosity threshold set to level {:d}.", verbosity); 106 | 107 | console->debug("Running with the following commands/arguments/options:"); 108 | for (auto const& arg : docargs) 109 | console->debug("{:<13}: {}", arg.first, arg.second); 110 | 111 | // exposure 112 | exposure = strtof(docargs["--exposure"].asString().c_str(), (char **)NULL); 113 | console->info("Setting intensity scale to {:f}", powf(2.0f, exposure)); 114 | 115 | // gamma or sRGB 116 | if (docargs["--gamma"]) 117 | { 118 | sRGB = false; 119 | gamma = max(0.1f, strtof(docargs["--gamma"].asString().c_str(), (char **)NULL)); 120 | console->info("Setting gamma correction to g={:f}.", gamma); 121 | } 122 | else 123 | console->info("Using sRGB response curve."); 124 | 125 | // dithering 126 | dither = !docargs["--no-dither"].asBool(); 127 | 128 | // list of filenames 129 | inFiles = docargs["FILE"].asStringList(); 130 | #endif 131 | 132 | console->info("Launching GUI."); 133 | nanogui::init(); 134 | 135 | #if defined(__APPLE__) 136 | if (launched_from_finder) 137 | nanogui::chdir_to_bundle_parent(); 138 | #endif 139 | 140 | { 141 | nanogui::ref viewer = new HDRViewScreen(exposure, gamma, sRGB, dither, inFiles); 142 | viewer->setVisible(true); 143 | nanogui::mainloop(); 144 | } 145 | 146 | nanogui::shutdown(); 147 | } 148 | // Exceptions will only be thrown upon failed logger or sink construction (not during logging) 149 | catch (const spd::spdlog_ex& e) 150 | { 151 | fprintf(stderr, "Log init failed: %s\n", e.what()); 152 | return 1; 153 | } 154 | catch (const std::exception &e) 155 | { 156 | spd::get("console")->critical("Error: {}", e.what()); 157 | fprintf(stderr, "%s", USAGE); 158 | return -1; 159 | } 160 | 161 | return EXIT_SUCCESS; 162 | } 163 | -------------------------------------------------------------------------------- /src/HelpWindow.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 | // Adapted from tev: 7 | // This file was developed by Thomas Müller . 8 | // It is published under the BSD 3-Clause License within the LICENSE file. 9 | 10 | #include "HelpWindow.h" 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include "Well.h" 19 | 20 | using namespace nanogui; 21 | using namespace std; 22 | 23 | #ifdef __APPLE__ 24 | string HelpWindow::COMMAND = "Cmd"; 25 | #else 26 | string HelpWindow::COMMAND = "Ctrl"; 27 | #endif 28 | 29 | #ifdef __APPLE__ 30 | string HelpWindow::ALT = "Opt"; 31 | #else 32 | string HelpWindow::ALT = "Alt"; 33 | #endif 34 | 35 | HelpWindow::HelpWindow(Widget *parent, function closeCallback) 36 | : Window{parent, "Help"}, mCloseCallback{closeCallback} 37 | { 38 | 39 | auto closeButton = new Button{buttonPanel(), "", ENTYPO_ICON_CROSS}; 40 | closeButton->setCallback(mCloseCallback); 41 | 42 | setLayout(new GroupLayout()); 43 | 44 | auto addRow = [](Widget* current, string keys, string desc) 45 | { 46 | auto row = new Widget(current); 47 | row->setLayout(new BoxLayout(Orientation::Horizontal, Alignment::Fill, 0, 0)); 48 | auto descWidget = new Label(row, desc, "sans", 14); 49 | descWidget->setFixedWidth(185); 50 | new Label{row, keys, "sans-bold", 14}; 51 | }; 52 | 53 | new Label(this, "About", "sans-bold", 18); 54 | 55 | auto copyW = new Widget(this); 56 | copyW->setLayout(new BoxLayout(Orientation::Horizontal, Alignment::Fill, 0, 0)); 57 | auto copy = new Label(copyW, "HDRView " HDRVIEW_VERSION ". Copyright (c) Wojciech Jarosz\n\n" 58 | "HDRView is a simple research-oriented tool for examining, " 59 | "comparing, manipulating, and converting high-dynamic range images.\n\n" 60 | "HDRView is freely available under a 3-clause BSD license."); 61 | copy->setFixedWidth(715); 62 | 63 | new Label(this, "Keybindings", "sans-bold", 18); 64 | 65 | auto keyBindingsWidget = new Well(this); 66 | keyBindingsWidget->setLayout(new BoxLayout(Orientation::Horizontal, Alignment::Fill, 10, 0)); 67 | 68 | auto newColumn = [keyBindingsWidget]() 69 | { 70 | auto w = new Widget(keyBindingsWidget); 71 | w->setLayout(new GroupLayout(0)); 72 | w->setFixedWidth(350); 73 | return w; 74 | }; 75 | 76 | auto column = newColumn(); 77 | 78 | new Label(column, "Images and Layer List", "sans-bold", 16); 79 | auto imageLoading = new Widget(column); 80 | imageLoading->setLayout(new BoxLayout(Orientation::Vertical, Alignment::Fill, 0, 0)); 81 | 82 | addRow(imageLoading, COMMAND + "+O", "Open Image"); 83 | addRow(imageLoading, COMMAND + "+S", "Save Image"); 84 | addRow(imageLoading, COMMAND + "+W or Delete", "Close Image"); 85 | addRow(imageLoading, COMMAND + "+Shift+W", "Close All Images"); 86 | addRow(imageLoading, "Left Click", "Select Image"); 87 | addRow(imageLoading, "Shift+Left Click", "Select/Deselect Reference Image"); 88 | addRow(imageLoading, "1…9", "Select the N-th Image"); 89 | addRow(imageLoading, "Down / Up", "Select Previous/Next Image"); 90 | addRow(imageLoading, COMMAND + "+Down / " + COMMAND + "+Up", "Send Image Forward/Backward"); 91 | addRow(imageLoading, ALT + "+Tab", "Jump Back To Previously Selected Image"); 92 | addRow(imageLoading, COMMAND + "+F", "Find Image"); 93 | 94 | new Label(column, "Display/Tonemapping Options", "sans-bold", 16); 95 | auto imageSelection = new Widget(column); 96 | imageSelection->setLayout(new BoxLayout(Orientation::Vertical, Alignment::Fill, 0, 0)); 97 | 98 | addRow(imageSelection, "E / Shift+E", "Decrease/Increase Exposure"); 99 | addRow(imageSelection, "G / Shift+G", "Decrease/Increase Gamma"); 100 | addRow(imageSelection, "R", "Reset tonemapping"); 101 | addRow(imageSelection, "N", "Normalize Image to [0,1]"); 102 | addRow(imageSelection, COMMAND + "+1…7", "Cycle through Color Channels"); 103 | addRow(imageSelection, "Shift+1…8", "Cycle through Blend Modes"); 104 | 105 | column = newColumn(); 106 | 107 | new Label(column, "Image Edits", "sans-bold", 16); 108 | auto edits = new Widget(column); 109 | edits->setLayout(new BoxLayout(Orientation::Vertical, Alignment::Fill, 0, 0)); 110 | 111 | addRow(edits, "F", "Flip image about horizontal axis"); 112 | addRow(edits, "M", "Mirror image about vertical axis"); 113 | addRow(edits, COMMAND + "+Z / " + COMMAND + "+Shift+Z", "Undo/Redo"); 114 | 115 | new Label(column, "Panning/Zooming", "sans-bold", 16); 116 | auto panningZooming = new Widget(column); 117 | panningZooming->setLayout(new BoxLayout(Orientation::Vertical, Alignment::Fill, 0, 0)); 118 | 119 | addRow(panningZooming, "Left Click+Drag / Shift+Scroll", "Pan image"); 120 | addRow(panningZooming, "Scroll", "Zoom In and Out Continuously"); 121 | addRow(panningZooming, "- / +", "Zoom In and Out by Powers of 2"); 122 | addRow(panningZooming, "Space", "Re-Center View"); 123 | addRow(panningZooming, COMMAND + "+0", "Fit Image to Screen"); 124 | 125 | new Label(column, "Interface", "sans-bold", 16); 126 | auto interface = new Widget(column); 127 | interface->setLayout(new BoxLayout(Orientation::Vertical, Alignment::Fill, 0, 0)); 128 | 129 | addRow(interface, "H", "Show/Hide Help (this Window)"); 130 | addRow(interface, "T", "Show/Hide the Top Toolbar"); 131 | addRow(interface, "Tab", "Show/Hide the Side Panel"); 132 | addRow(interface, "Shift+Tab", "Show/Hide All Panels"); 133 | addRow(interface, COMMAND + "+Q or Esc", "Quit"); 134 | } 135 | 136 | bool HelpWindow::keyboardEvent(int key, int scancode, int action, int modifiers) 137 | { 138 | if (Window::keyboardEvent(key, scancode, action, modifiers)) 139 | { 140 | return true; 141 | } 142 | 143 | if (key == GLFW_KEY_ESCAPE) 144 | { 145 | mCloseCallback(); 146 | return true; 147 | } 148 | 149 | return false; 150 | } 151 | -------------------------------------------------------------------------------- /src/PPM.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 "PPM.h" 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | using namespace std; 15 | 16 | namespace 17 | { 18 | 19 | struct RGB 20 | { 21 | unsigned char r; 22 | unsigned char g; 23 | unsigned char b; 24 | }; 25 | 26 | } // end namespace 27 | 28 | bool isPPMImage(const char *filename) 29 | { 30 | FILE *infile = nullptr; 31 | int numInputsRead = 0; 32 | char buffer[256]; 33 | 34 | try 35 | { 36 | infile = fopen(filename, "rb"); 37 | 38 | if (!infile) 39 | throw std::runtime_error("cannot open file."); 40 | 41 | if ((fgets(buffer, 256, infile) == nullptr) || (buffer[0] != 'P') || (buffer[1] != '6')) 42 | throw std::runtime_error("image is not a binary PPM file."); 43 | 44 | // skip comments 45 | do {fgets(buffer, sizeof(buffer), infile);} while(buffer[0] == '#'); 46 | 47 | // read image size 48 | int width, height; 49 | numInputsRead = sscanf(buffer, "%d %d", &width, &height); 50 | if (numInputsRead != 2) 51 | throw runtime_error("could not read number of channels in header."); 52 | 53 | // skip comments 54 | do {fgets(buffer, sizeof(buffer), infile);} while(buffer[0] == '#'); 55 | 56 | // read maximum pixel value (usually 255) 57 | int colors; 58 | numInputsRead = sscanf(buffer, "%d", &colors); 59 | if (numInputsRead != 1) 60 | throw runtime_error("could not read max color value."); 61 | 62 | if (colors != 255) 63 | throw std::runtime_error("max color value must be 255."); 64 | 65 | fclose(infile); 66 | return true; 67 | } 68 | catch (const std::exception &e) 69 | { 70 | if (infile) 71 | fclose(infile); 72 | return false; 73 | } 74 | } 75 | 76 | 77 | float * loadPPMImage(const char *filename, int *width, int *height, int *numChannels) 78 | { 79 | FILE *infile = nullptr; 80 | float * img = nullptr; 81 | int colors; 82 | int numInputsRead = 0; 83 | float invColors; 84 | char buffer[256]; 85 | RGB *buf = nullptr; 86 | 87 | try 88 | { 89 | infile = fopen(filename, "rb"); 90 | 91 | if (!infile) 92 | throw std::runtime_error("cannot open file."); 93 | 94 | if ((fgets(buffer, 256, infile) == nullptr) || (buffer[0] != 'P') || (buffer[1] != '6')) 95 | throw std::runtime_error("image is not a binary PPM file."); 96 | 97 | *numChannels = 3; 98 | 99 | // skip comments 100 | do {fgets(buffer, sizeof(buffer), infile);} while(buffer[0] == '#'); 101 | 102 | // read image size 103 | numInputsRead = sscanf(buffer, "%d %d", width, height); 104 | if (numInputsRead != 2) 105 | throw runtime_error("could not read number of channels in header."); 106 | 107 | // skip comments 108 | do {fgets(buffer, sizeof(buffer), infile);} while(buffer[0] == '#'); 109 | 110 | // read maximum pixel value (usually 255) 111 | numInputsRead = sscanf(buffer, "%d", &colors); 112 | if (numInputsRead != 1) 113 | throw runtime_error("could not read max color value."); 114 | invColors = 1.0f/colors; 115 | 116 | if (colors != 255) 117 | throw std::runtime_error("max color value must be 255."); 118 | 119 | img = new float [*width * *height * 3]; 120 | 121 | buf = new RGB[*width]; 122 | for (int y = 0; y < *height; ++y) 123 | { 124 | if (fread(buf, *width * sizeof(RGB), 1, infile) != 1) 125 | throw std::runtime_error("cannot read pixel data."); 126 | 127 | RGB *cur = buf; 128 | float * curLine = &img[y * *width * 3]; 129 | for (int x = 0; x < *width; x++) 130 | { 131 | curLine[3*x + 0] = cur->r * invColors; 132 | curLine[3*x + 1] = cur->g * invColors; 133 | curLine[3*x + 2] = cur->b * invColors; 134 | cur++; 135 | } 136 | } 137 | delete [] buf; 138 | 139 | fclose(infile); 140 | return img; 141 | } 142 | catch (const std::exception &e) 143 | { 144 | delete [] buf; 145 | delete [] img; 146 | if (infile) 147 | fclose(infile); 148 | throw std::runtime_error(string("ERROR in loadPPMImage: ") + 149 | string(e.what()) + 150 | string(" Unable to read PPM file '") + 151 | filename + "'"); 152 | } 153 | } 154 | 155 | 156 | bool writePPMImage(const char *filename, int width, int height, int numChannels, const unsigned char *data) 157 | { 158 | FILE *outfile = nullptr; 159 | 160 | try 161 | { 162 | outfile = fopen(filename, "wb"); 163 | if (!outfile) 164 | throw std::runtime_error("cannot open file."); 165 | 166 | // write header 167 | fprintf(outfile, "P6\n"); 168 | fprintf(outfile, "%d %d\n", width, height); 169 | fprintf(outfile, "255\n"); 170 | 171 | auto numChars = static_cast(numChannels*width*height); 172 | if (fwrite(data, sizeof(unsigned char), numChars, outfile) != numChars) 173 | throw std::runtime_error("cannot write pixel data."); 174 | 175 | fclose (outfile); 176 | return true; 177 | } 178 | catch (const std::exception &e) 179 | { 180 | if (outfile) 181 | fclose (outfile); 182 | throw std::runtime_error(string("ERROR in writePPMImage: ") + 183 | string(e.what()) + 184 | string(" Unable to write PPM file '") + 185 | string(filename) + "'"); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /ext/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # prevent glfw from changing the directory on macOS bundles 2 | SET(GLFW_USE_CHDIR OFF CACHE BOOL "Prevent glfwInit from chdir to Contents/Resources" FORCE) 3 | 4 | #============================================================================ 5 | # Check if we have all the submodules checked out 6 | #============================================================================ 7 | if(NOT IS_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/nanogui/ext/glfw") 8 | message(FATAL_ERROR "The HDRView dependency repositories (NanoGUI, GLFW, etc.) are missing! " 9 | "You probably did not clone the project with --recursive. It is possible to recover " 10 | "by calling \"git submodule update --init --recursive\"") 11 | endif() 12 | 13 | #============================================================================ 14 | # Build ZLIB on Windows (needed for OpenEXR) 15 | #============================================================================ 16 | if (WIN32) 17 | # Build zlib (only on Windows) 18 | set(ZLIB_BUILD_STATIC_LIBS ON CACHE BOOL " " FORCE) 19 | set(ZLIB_BUILD_SHARED_LIBS OFF CACHE BOOL " " FORCE) 20 | add_subdirectory(zlib) 21 | 22 | set(ZLIB_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/zlib" CACHE PATH " " FORCE) 23 | set(ZLIB_LIBRARY "${CMAKE_CURRENT_BINARY_DIR}/zlib/$/zlibstatic.lib" CACHE FILEPATH " " FORCE) 24 | 25 | set_property(TARGET zlibstatic PROPERTY FOLDER "dependencies") 26 | include_directories(${ZLIB_INCLUDE_DIR} "${CMAKE_CURRENT_BINARY_DIR}/zlib") 27 | endif() 28 | 29 | #============================================================================ 30 | # Build TinyNPY 31 | #============================================================================ 32 | if (FALSE) 33 | set(BUILD_DEMO OFF CACHE BOOL " " FORCE) 34 | set(BUILD_SHARED_LIBS OFF CACHE BOOL " " FORCE) 35 | set(BUILD_STATIC_LIBS ON CACHE BOOL " " FORCE) 36 | add_subdirectory(tinynpy EXCLUDE_FROM_ALL) 37 | set_property(TARGET TinyNPYstatic PROPERTY FOLDER "dependencies") 38 | else() 39 | add_library(TinyNPY STATIC 40 | ${CMAKE_CURRENT_SOURCE_DIR}/tinynpy/TinyNPY.cpp 41 | ${CMAKE_CURRENT_SOURCE_DIR}/tinynpy/TinyNPY.h) 42 | target_link_libraries(TinyNPY PRIVATE zlibstatic) 43 | set_property(TARGET TinyNPY PROPERTY FOLDER "dependencies") 44 | endif() 45 | 46 | #============================================================================ 47 | # Build OpenEXR 48 | #============================================================================ 49 | set(ILMBASE_BUILD_SHARED_LIBS OFF CACHE BOOL " " FORCE) 50 | set(OPENEXR_BUILD_SHARED_LIBS OFF CACHE BOOL " " FORCE) 51 | set(ILMBASE_NAMESPACE_VERSIONING OFF CACHE BOOL " " FORCE) 52 | set(OPENEXR_NAMESPACE_VERSIONING OFF CACHE BOOL " " FORCE) 53 | add_subdirectory(openexr EXCLUDE_FROM_ALL) 54 | set_property(TARGET IexMath eLut toFloat b44ExpLogTable dwaLookups CopyIlmBaseLibs IlmThread Half Iex Imath IlmImf IlmImf-obj PROPERTY FOLDER "dependencies") 55 | 56 | #============================================================================ 57 | # Build NanoGUI 58 | #============================================================================ 59 | set(NANOGUI_BUILD_EXAMPLE OFF CACHE BOOL " " FORCE) 60 | set(NANOGUI_BUILD_SHARED OFF CACHE BOOL " " FORCE) 61 | set(NANOGUI_BUILD_PYTHON OFF CACHE BOOL " " FORCE) 62 | set(NANOGUI_INSTALL OFF CACHE BOOL " " FORCE) 63 | add_subdirectory(nanogui) 64 | set_property(TARGET glfw glfw_objects nanogui nanogui-obj PROPERTY FOLDER "dependencies") 65 | 66 | #============================================================================ 67 | # Build docopt.cpp 68 | #============================================================================ 69 | add_subdirectory(docopt EXCLUDE_FROM_ALL) 70 | if (WIN32) 71 | add_definitions("-DDOCTOPT_USE_BOOST_REGEX") 72 | set(Boost_USE_STATIC_LIBS ON) 73 | set(Boost_USE_STATIC_RUNTIME ON) 74 | find_package(Boost 1.53 REQUIRED COMPONENTS regex) 75 | endif() 76 | set_property(TARGET docopt docopt_s PROPERTY FOLDER "dependencies") 77 | 78 | #============================================================================ 79 | # Handle zlib and boost dependencies on windows 80 | #============================================================================ 81 | if (WIN32) 82 | add_dependencies(IlmImf zlibstatic) 83 | endif() 84 | 85 | #============================================================================ 86 | # Define variables for the include directories for the various dependencies 87 | # and export them to the parent scope 88 | #============================================================================ 89 | set(OPENEXR_INCLUDE_DIRS 90 | ${CMAKE_CURRENT_SOURCE_DIR}/openexr/IlmBase/Imath 91 | ${CMAKE_CURRENT_SOURCE_DIR}/openexr/IlmBase/Iex 92 | ${CMAKE_CURRENT_SOURCE_DIR}/openexr/IlmBase/Half 93 | ${CMAKE_CURRENT_SOURCE_DIR}/openexr/OpenEXR/IlmImf 94 | ${CMAKE_CURRENT_BINARY_DIR}/openexr/OpenEXR/config 95 | ${CMAKE_CURRENT_BINARY_DIR}/openexr/IlmBase/config PARENT_SCOPE) 96 | set(GLFW_INCLUDE_DIR 97 | ${CMAKE_CURRENT_SOURCE_DIR}/nanogui/ext/glfw/include PARENT_SCOPE) 98 | set(GLEW_INCLUDE_DIR 99 | ${CMAKE_CURRENT_SOURCE_DIR}/nanogui/ext/glew/include PARENT_SCOPE) 100 | set(NANOVG_INCLUDE_DIR 101 | ${CMAKE_CURRENT_SOURCE_DIR}/nanogui/ext/nanovg/src PARENT_SCOPE) 102 | set(NANOGUI_INCLUDE_DIR 103 | ${CMAKE_CURRENT_SOURCE_DIR}/nanogui/include PARENT_SCOPE) 104 | set(NANOGUI_EXTRA_INCS ${NANOGUI_EXTRA_INCS} PARENT_SCOPE) 105 | set(NANOGUI_EXTRA_DEFS ${NANOGUI_EXTRA_DEFS} PARENT_SCOPE) 106 | set(NANOGUI_EXTRA_LIBS ${NANOGUI_EXTRA_LIBS} PARENT_SCOPE) 107 | set(EIGEN_INCLUDE_DIR 108 | ${CMAKE_CURRENT_SOURCE_DIR}/nanogui/ext/eigen PARENT_SCOPE) 109 | set(DOCOPT_INCLUDE_DIR 110 | ${CMAKE_CURRENT_SOURCE_DIR}/docopt PARENT_SCOPE) 111 | set(TINYDIR_INCLUDE_DIR 112 | ${CMAKE_CURRENT_SOURCE_DIR}/tinydir PARENT_SCOPE) 113 | set(TINYDNG_INCLUDE_DIR 114 | ${CMAKE_CURRENT_SOURCE_DIR}/tinydngloader PARENT_SCOPE) 115 | set(TINYNPY_INCLUDE_DIR 116 | ${CMAKE_CURRENT_SOURCE_DIR}/tinynpy PARENT_SCOPE) 117 | set(STB_INCLUDE_DIR 118 | ${CMAKE_CURRENT_SOURCE_DIR}/stb PARENT_SCOPE) 119 | set(SPDLOG_INCLUDE_DIR 120 | ${CMAKE_CURRENT_SOURCE_DIR}/spdlog/include PARENT_SCOPE) 121 | -------------------------------------------------------------------------------- /src/GLImage.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 // for uint32_t 10 | #include // for Vector2i, Matrix4f, Vector3f 11 | #include // for function 12 | #include // for string 13 | #include // for swap 14 | #include // for vector, allocator 15 | #include 16 | #include "HDRImage.h" // for HDRImage 17 | #include "Fwd.h" // for HDRImage 18 | #include "CommandHistory.h" 19 | #include "Async.h" 20 | #include 21 | #include 22 | 23 | 24 | struct ImageStatistics 25 | { 26 | float minimum; 27 | float average; 28 | float maximum; 29 | float exposure; 30 | 31 | enum AxisScale : int 32 | { 33 | ELinear = 0, 34 | ESRGB = 1, 35 | ELog = 2, 36 | ENumAxisScales = 3 37 | }; 38 | 39 | struct Histogram 40 | { 41 | Eigen::MatrixX3f values; 42 | Eigen::VectorXf xTicks; 43 | std::vector xTickLabels; 44 | }; 45 | 46 | Histogram histogram[ENumAxisScales]; 47 | 48 | 49 | static std::shared_ptr computeStatistics(const HDRImage &img, float exposure); 50 | }; 51 | 52 | 53 | /*! 54 | * A helper class that uploads a texture to the GPU incrementally in smaller chunks. 55 | * To avoid stalling the main rendering thread, chunks are uploaded until a 56 | * timeout has been reached. 57 | */ 58 | class LazyGLTextureLoader 59 | { 60 | public: 61 | ~LazyGLTextureLoader(); 62 | 63 | bool dirty() const {return m_dirty;} 64 | void setDirty() {m_dirty = true; m_nextScanline = 0; m_uploadTime = 0;} 65 | 66 | /*! 67 | * Incrementally upload a portion of an image to the GPU, returning shortly after the 68 | * specified timeout duration. Should be called repeatedly until it returns True. 69 | * 70 | * @param img The image to upload 71 | * @param timeout Return after this many milliseconds 72 | * @param mipLevel Which miplevel to upload 73 | * @param chunkSize The target number of pixels to upload 74 | * @return True iff uploading is done 75 | */ 76 | bool uploadToGPU(const std::shared_ptr & img, 77 | int timeout = 100, 78 | int chunkSize = 128 * 128); 79 | 80 | GLuint textureID() const {return m_texture;} 81 | 82 | private: 83 | GLuint m_texture = 0; 84 | int m_nextScanline = -1; 85 | bool m_dirty = false; 86 | double m_uploadTime = 0.0; 87 | }; 88 | 89 | /*! 90 | A class which encapsulates a single HDRImage, a corresponding OpenGL texture, and histogram. 91 | Access to the HDRImage is provided only through the modify function, which accepts undo-able image editing commands 92 | */ 93 | class GLImage 94 | { 95 | public: 96 | using LazyHistogram = AsyncTask>; 97 | using LazyHistogramPtr = std::shared_ptr; 98 | using ConstModifyingTask = std::shared_ptr>; 99 | using ModifyingTask = std::shared_ptr>; 100 | using VoidVoidFunc = std::function; 101 | 102 | 103 | GLImage(); 104 | ~GLImage(); 105 | 106 | bool canModify() const; 107 | float progress() const; 108 | void asyncModify(const ImageCommand & command); 109 | void asyncModify(const ImageCommandWithProgress & command); 110 | bool isModified() const; 111 | bool undo(); 112 | bool redo(); 113 | bool hasUndo() const; 114 | bool hasRedo() const; 115 | 116 | GLuint glTextureId() const; 117 | void setFilename(const std::string & filename) { m_filename = filename; } 118 | std::string filename() const { return m_filename; } 119 | bool isNull() const { checkAsyncResult(); return !m_image || m_image->isNull(); } 120 | const HDRImage & image() const { checkAsyncResult(); return *m_image; } 121 | int width() const { checkAsyncResult(); return m_image->width(); } 122 | int height() const { checkAsyncResult(); return m_image->height(); } 123 | Eigen::Vector2i size() const { return isNull() ? Eigen::Vector2i(0,0) : Eigen::Vector2i(m_image->width(), m_image->height()); } 124 | bool contains(const Eigen::Vector2i& p) const {return (p.array() >= 0).all() && (p.array() < size().array()).all();} 125 | 126 | bool load(const std::string & filename); 127 | bool save(const std::string & filename, 128 | float gain, float gamma, 129 | bool sRGB, bool dither) const; 130 | 131 | float histogramExposure() const { return m_cachedHistogramExposure; } 132 | bool histogramDirty() const { return m_histogramDirty; } 133 | LazyHistogramPtr histograms() const { return m_histograms; } 134 | void recomputeHistograms(float exposure) const; 135 | 136 | /// Callback executed whenever an image finishes being modified, e.g. via @ref asyncModify 137 | const VoidVoidFunc & imageModifyDoneCallback() const { return m_imageModifyDoneCallback; } 138 | void setImageModifyDoneCallback(const VoidVoidFunc & callback) { m_imageModifyDoneCallback = callback; } 139 | 140 | private: 141 | bool checkAsyncResult() const; 142 | bool waitForAsyncResult() const; 143 | void uploadToGPU() const; 144 | void modifyFinished() const; 145 | 146 | mutable std::shared_ptr m_image; 147 | std::string m_filename; 148 | mutable LazyGLTextureLoader m_texture; 149 | mutable float m_cachedHistogramExposure; 150 | mutable std::atomic m_histogramDirty; 151 | mutable LazyHistogramPtr m_histograms; 152 | mutable CommandHistory m_history; 153 | 154 | mutable ModifyingTask m_asyncCommand = nullptr; 155 | mutable bool m_asyncRetrieved = false; 156 | 157 | // various callback functions 158 | VoidVoidFunc m_imageModifyDoneCallback; 159 | }; 160 | 161 | using ConstImagePtr = std::shared_ptr; 162 | using ImagePtr = std::shared_ptr; 163 | -------------------------------------------------------------------------------- /src/FilmicToneCurve.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 | // This file is a modified version of Filmic Worlds' FilmicToneCurve class: 7 | // https://github.com/johnhable/fw-public 8 | // which was released into the public domain through the CC0 Universal license 9 | 10 | #include "FilmicToneCurve.h" 11 | #include 12 | 13 | float FilmicToneCurve::CurveSegment::eval(float x) const 14 | { 15 | float x0 = (x - offsetX)*scaleX; 16 | float y0 = 0.0f; 17 | 18 | // log(0) is undefined but our function should evaluate to 0. There are better ways to handle this, 19 | // but it's doing it the slow way here for clarity. 20 | if (x0 > 0) 21 | y0 = std::exp(lnA + B*std::log(x0)); 22 | 23 | return y0*scaleY + offsetY; 24 | } 25 | 26 | float FilmicToneCurve::CurveSegment::evalInv(float y) const 27 | { 28 | float y0 = (y - offsetY)/scaleY; 29 | float x0 = 0.0f; 30 | 31 | // watch out for log(0) again 32 | if (y0 > 0) 33 | x0 = std::exp((std::log(y0) - lnA)/B); 34 | float x = x0/scaleX + offsetX; 35 | 36 | return x; 37 | } 38 | 39 | float FilmicToneCurve::FullCurve::eval(float srcX) const 40 | { 41 | float normX = srcX*invW; 42 | int index = (normX < x0) ? 0 : ((normX < x1) ? 1 : 2); 43 | CurveSegment segment = m_segments[index]; 44 | float ret = segment.eval(normX); 45 | return ret; 46 | } 47 | 48 | float FilmicToneCurve::FullCurve::evalInv(float y) const 49 | { 50 | int index = (y < y0) ? 0 : ((y < y1) ? 1 : 2); 51 | CurveSegment segment = m_segments[index]; 52 | 53 | float normX = segment.evalInv(y); 54 | return normX*W; 55 | } 56 | 57 | // find a function of the form: 58 | // f(x) = e^(lnA + Bln(x)) 59 | // where 60 | // f(0) = 0; not really a constraint 61 | // f(x0) = y0 62 | // f'(x0) = m 63 | static void solveAB(float& lnA, float& B, float x0, float y0, float m) 64 | { 65 | B = (m*x0)/y0; 66 | lnA = std::log(y0) - B*std::log(x0); 67 | } 68 | 69 | // convert to y=mx+b 70 | static void asSlopeIntercept(float& m, float& b, float x0, float x1, float y0, float y1) 71 | { 72 | float dy = (y1 - y0); 73 | float dx = (x1 - x0); 74 | if (dx == 0) 75 | m = 1.0f; 76 | else 77 | m = dy/dx; 78 | 79 | b = y0 - x0*m; 80 | } 81 | 82 | // f(x) = (mx+b)^g 83 | // f'(x) = gm(mx+b)^(g-1) 84 | static float evalDerivativeLinearGamma(float m, float b, float g, float x) 85 | { 86 | float ret = g*m*powf(m*x + b, g - 1.0f); 87 | return ret; 88 | } 89 | 90 | void FilmicToneCurve::createCurve(FullCurve& dstCurve, const CurveParamsDirect& srcParams) 91 | { 92 | CurveParamsDirect params = srcParams; 93 | 94 | dstCurve.reset(); 95 | dstCurve.W = srcParams.W; 96 | dstCurve.invW = 1.0f/srcParams.W; 97 | 98 | // normalize params to 1.0 range 99 | params.W = 1.0f; 100 | params.x0 /= srcParams.W; 101 | params.x1 /= srcParams.W; 102 | params.overshootX = srcParams.overshootX/srcParams.W; 103 | 104 | float toeM = 0.0f; 105 | float shoulderM = 0.0f; 106 | { 107 | float m, b; 108 | asSlopeIntercept(m, b, params.x0, params.x1, params.y0, params.y1); 109 | 110 | float g = srcParams.gamma; 111 | 112 | // base function of linear section plus gamma is 113 | // y = (mx+b)^g 114 | 115 | // which we can rewrite as 116 | // y = exp(g*ln(m) + g*ln(x+b/m)) 117 | 118 | // and our evaluation function is (skipping the if parts): 119 | /* 120 | float x0 = (x - offsetX)*scaleX; 121 | y0 = expf(lnA + B*logf(x0)); 122 | return y0*scaleY + offsetY; 123 | */ 124 | 125 | CurveSegment midSegment; 126 | midSegment.offsetX = -(b/m); 127 | midSegment.offsetY = 0.0f; 128 | midSegment.scaleX = 1.0f; 129 | midSegment.scaleY = 1.0f; 130 | midSegment.lnA = g*std::log(m); 131 | midSegment.B = g; 132 | 133 | dstCurve.m_segments[1] = midSegment; 134 | 135 | toeM = evalDerivativeLinearGamma(m, b, g, params.x0); 136 | shoulderM = evalDerivativeLinearGamma(m, b, g, params.x1); 137 | 138 | // apply gamma to endpoints 139 | params.y0 = std::max(0.f, std::pow(params.y0, params.gamma)); 140 | params.y1 = std::max(1e-5f, std::pow(params.y1, params.gamma)); 141 | 142 | params.overshootY = std::pow(1.0f + params.overshootY, params.gamma) - 1.0f; 143 | } 144 | 145 | spdlog::get("console")->debug("\n" 146 | "x0: {}\t\t{}\n" 147 | "y0: {}\t\t{}\n" 148 | "x1: {}\t\t{}\n" 149 | "y1: {}\t\t{}\n" 150 | "W: {}\t\t{}\n" 151 | "gamma: {}\t\t{}\n" 152 | "overshootX: {}\t\t{}\n" 153 | "overshootY: {}\t\t{}\n", srcParams.x0, params.x0, srcParams.y0, params.y0, 154 | srcParams.x1, params.x1, srcParams.y1, params.y1, srcParams.W, params.W, 155 | srcParams.gamma, params.gamma, srcParams.overshootX, params.overshootX, 156 | srcParams.overshootY, params.overshootY); 157 | 158 | dstCurve.x0 = params.x0; 159 | dstCurve.x1 = params.x1; 160 | dstCurve.y0 = params.y0; 161 | dstCurve.y1 = params.y1; 162 | 163 | // toe section 164 | { 165 | CurveSegment toeSegment; 166 | toeSegment.offsetX = 0; 167 | toeSegment.offsetY = 0.0f; 168 | toeSegment.scaleX = 1.0f; 169 | toeSegment.scaleY = 1.0f; 170 | 171 | solveAB(toeSegment.lnA, toeSegment.B, params.x0, params.y0, toeM); 172 | dstCurve.m_segments[0] = toeSegment; 173 | } 174 | 175 | // shoulder section 176 | { 177 | // use the simple version that is usually too flat 178 | CurveSegment shoulderSegment; 179 | 180 | float x0 = (1.0f + params.overshootX) - params.x1; 181 | float y0 = (1.0f + params.overshootY) - params.y1; 182 | 183 | float lnA = 0.0f; 184 | float B = 0.0f; 185 | solveAB(lnA, B, x0, y0, shoulderM); 186 | 187 | shoulderSegment.offsetX = (1.0f + params.overshootX); 188 | shoulderSegment.offsetY = (1.0f + params.overshootY); 189 | 190 | shoulderSegment.scaleX = -1.0f; 191 | shoulderSegment.scaleY = -1.0f; 192 | shoulderSegment.lnA = lnA; 193 | shoulderSegment.B = B; 194 | 195 | dstCurve.m_segments[2] = shoulderSegment; 196 | } 197 | 198 | // Normalize so that we hit 1.0 at our white point. We wouldn't have do this if we 199 | // skipped the overshoot part. 200 | { 201 | // evaluate shoulder at the end of the curve 202 | float scale = dstCurve.m_segments[2].eval(1.0f); 203 | float invScale = 1.0f/scale; 204 | 205 | dstCurve.m_segments[0].offsetY *= invScale; 206 | dstCurve.m_segments[0].scaleY *= invScale; 207 | 208 | dstCurve.m_segments[1].offsetY *= invScale; 209 | dstCurve.m_segments[1].scaleY *= invScale; 210 | 211 | dstCurve.m_segments[2].offsetY *= invScale; 212 | dstCurve.m_segments[2].scaleY *= invScale; 213 | } 214 | 215 | } 216 | 217 | void FilmicToneCurve::calcDirectParamsFromUser(CurveParamsDirect& dstParams, const CurveParamsUser& srcParams) 218 | { 219 | dstParams = CurveParamsDirect(); 220 | 221 | float toeStrength = srcParams.toeStrength; 222 | float toeLength = srcParams.toeLength; 223 | float shoulderStrength = srcParams.shoulderStrength; 224 | float shoulderLength = srcParams.shoulderLength; 225 | 226 | float shoulderAngle = srcParams.shoulderAngle; 227 | float gamma = srcParams.gamma; 228 | 229 | // This is not actually the display gamma. It's just a UI space to avoid having to 230 | // enter small numbers for the input. 231 | float perceptualGamma = 2.2f; 232 | 233 | // constraints 234 | { 235 | toeLength = pow(clamp01(toeLength), perceptualGamma); 236 | toeStrength = clamp01(toeStrength); 237 | shoulderAngle = clamp01(shoulderAngle); 238 | shoulderLength = std::max(1e-5f, clamp01(shoulderLength)); 239 | 240 | shoulderStrength = std::max(0.0f, shoulderStrength); 241 | } 242 | 243 | // apply base params 244 | { 245 | // toe goes from 0 to 0.5 246 | float x0 = toeLength*.5f; 247 | float y0 = (1.0f - toeStrength)*x0; // lerp from 0 to x0 248 | 249 | float remainingY = 1.0f - y0; 250 | 251 | float initialW = x0 + remainingY; 252 | 253 | float y1_offset = (1.0f - shoulderLength)*remainingY; 254 | float x1 = x0 + y1_offset; 255 | float y1 = y0 + y1_offset; 256 | 257 | // filmic shoulder strength is in F stops 258 | float extraW = exp2(shoulderStrength) - 1.0f; 259 | 260 | float W = initialW + extraW; 261 | 262 | dstParams.x0 = x0; 263 | dstParams.y0 = y0; 264 | dstParams.x1 = x1; 265 | dstParams.y1 = y1; 266 | dstParams.W = W; 267 | 268 | // bake the linear to gamma space conversion 269 | dstParams.gamma = gamma; 270 | } 271 | 272 | dstParams.overshootX = (dstParams.W*2.0f)*shoulderAngle*shoulderStrength; 273 | dstParams.overshootY = 0.5f*shoulderAngle*shoulderStrength; 274 | } 275 | 276 | 277 | -------------------------------------------------------------------------------- /resources/MacOSXBundleInfo.plist.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | English 7 | CFBundleExecutable 8 | ${MACOSX_BUNDLE_EXECUTABLE_NAME} 9 | CFBundleGetInfoString 10 | ${MACOSX_BUNDLE_INFO_STRING} 11 | CFBundleIconFile 12 | ${MACOSX_BUNDLE_ICON_FILE} 13 | CFBundleIdentifier 14 | ${MACOSX_BUNDLE_GUI_IDENTIFIER} 15 | CFBundleInfoDictionaryVersion 16 | 6.0 17 | CFBundleLongVersionString 18 | ${MACOSX_BUNDLE_LONG_VERSION_STRING} 19 | CFBundleName 20 | ${MACOSX_BUNDLE_BUNDLE_NAME} 21 | CFBundlePackageType 22 | APPL 23 | CFBundleShortVersionString 24 | ${MACOSX_BUNDLE_SHORT_VERSION_STRING} 25 | CFBundleSignature 26 | ???? 27 | CFBundleVersion 28 | ${MACOSX_BUNDLE_BUNDLE_VERSION} 29 | NSHumanReadableCopyright 30 | ${MACOSX_BUNDLE_COPYRIGHT} 31 | NSHighResolutionCapable 32 | True 33 | CFBundleDocumentTypes 34 | 35 | 36 | CFBundleTypeExtensions 37 | 38 | exr 39 | 40 | CFBundleTypeName 41 | OpenEXR image 42 | CFBundleTypeRole 43 | Viewer 44 | LSHandlerRank 45 | Default 46 | LSItemContentTypes 47 | 48 | com.ilm.openexr-image 49 | 50 | 51 | 52 | CFBundleTypeExtensions 53 | 54 | bmp 55 | 56 | CFBundleTypeName 57 | BMP image 58 | CFBundleTypeRole 59 | Viewer 60 | LSHandlerRank 61 | Default 62 | LSItemContentTypes 63 | 64 | com.microsoft.bmp 65 | 66 | 67 | 68 | CFBundleTypeExtensions 69 | 70 | gif 71 | 72 | CFBundleTypeName 73 | GIF image 74 | CFBundleTypeRole 75 | Viewer 76 | LSHandlerRank 77 | Default 78 | LSItemContentTypes 79 | 80 | com.compuserve.gif 81 | 82 | 83 | 84 | CFBundleTypeExtensions 85 | 86 | jpg 87 | jpeg 88 | jpe 89 | thm 90 | 91 | CFBundleTypeName 92 | JPEG image 93 | CFBundleTypeRole 94 | Viewer 95 | LSHandlerRank 96 | Default 97 | LSItemContentTypes 98 | 99 | public.jpeg 100 | 101 | 102 | 103 | CFBundleTypeExtensions 104 | 105 | pic 106 | hdr 107 | 108 | CFBundleTypeName 109 | Radiance image 110 | CFBundleTypeRole 111 | Viewer 112 | LSHandlerRank 113 | Default 114 | LSItemContentTypes 115 | 116 | public.radiance 117 | 118 | 119 | 120 | CFBundleTypeExtensions 121 | 122 | pfm 123 | pnm 124 | 125 | CFBundleTypeName 126 | Portable float map image 127 | CFBundleTypeRole 128 | Viewer 129 | LSHandlerRank 130 | Default 131 | LSItemContentTypes 132 | 133 | public.pfm 134 | 135 | 136 | 137 | CFBundleTypeExtensions 138 | 139 | png 140 | 141 | CFBundleTypeName 142 | PNG image 143 | CFBundleTypeRole 144 | Viewer 145 | LSHandlerRank 146 | Default 147 | LSItemContentTypes 148 | 149 | public.png 150 | 151 | 152 | 153 | CFBundleTypeExtensions 154 | 155 | tga 156 | 157 | CFBundleTypeName 158 | Targa image 159 | CFBundleTypeRole 160 | Viewer 161 | LSHandlerRank 162 | Default 163 | LSItemContentTypes 164 | 165 | com.truevision.tga-image 166 | 167 | 168 | 169 | CFBundleTypeExtensions 170 | 171 | psd 172 | 173 | CFBundleTypeName 174 | Photoshop image 175 | CFBundleTypeRole 176 | Viewer 177 | LSHandlerRank 178 | Default 179 | LSItemContentTypes 180 | 181 | com.adobe.photoshop-image 182 | 183 | 184 | 185 | CFBundleTypeExtensions 186 | 187 | pbm 188 | pgm 189 | ppm 190 | pnm 191 | 192 | CFBundleTypeName 193 | NetPBM image 194 | CFBundleTypeRole 195 | Viewer 196 | LSHandlerRank 197 | Default 198 | LSItemContentTypes 199 | 200 | net.sourceforge.netpbm.netpbm-image 201 | 202 | 203 | 204 | CFBundleTypeExtensions 205 | 206 | dng 207 | 208 | CFBundleTypeName 209 | Digital Negative image 210 | CFBundleTypeRole 211 | Viewer 212 | LSHandlerRank 213 | Default 214 | LSItemContentTypes 215 | 216 | com.adobe.raw-image 217 | 218 | 219 | 220 | CFBundleTypeName 221 | Image file 222 | CFBundleTypeRole 223 | Viewer 224 | LSHandlerRank 225 | Default 226 | LSItemContentTypes 227 | 228 | public.image 229 | 230 | 231 | 232 | CFBundleTypeExtensions 233 | 234 | * 235 | 236 | CFBundleTypeName 237 | Any 238 | CFBundleTypeRole 239 | Viewer 240 | 241 | 242 | CFBundleTypeRole 243 | Viewer 244 | LSHandlerRank 245 | Alternate 246 | LSItemContentTypes 247 | 248 | public.directory 249 | com.apple.bundle 250 | com.apple.resolvable 251 | 252 | 253 | 254 | 255 | NSSupportsAutomaticGraphicsSwitching 256 | 257 | 258 | 259 | -------------------------------------------------------------------------------- /src/MultiGraph.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 "MultiGraph.h" 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include "Common.h" 13 | #include "Colorspace.h" 14 | #include 15 | 16 | using std::string; 17 | using std::vector; 18 | 19 | namespace 20 | { 21 | const int hpad = 10; 22 | const int textPad = 4; 23 | } 24 | 25 | /*! 26 | * @param parent The parent widget 27 | * @param fg The foreground color of the first plot 28 | * @param v The value vector for the first plot 29 | */ 30 | MultiGraph::MultiGraph(Widget *parent, const Color & fg, const VectorXf & v) 31 | : Widget(parent), mBackgroundColor(20, 128), mTextColor(240, 192) 32 | { 33 | mForegroundColors.push_back(fg); 34 | mValues.push_back(v); 35 | } 36 | 37 | Vector2i MultiGraph::preferredSize(NVGcontext *) const 38 | { 39 | return Vector2i(256, 75); 40 | } 41 | 42 | Vector2f MultiGraph::graphCoordinateAt(const Vector2f& position) const 43 | { 44 | Vector2f topLeft(xPosition(0), yPosition(0)); 45 | Vector2f bottomRight(xPosition(1), yPosition(1)); 46 | Vector2f graphSize = bottomRight-topLeft; 47 | return (position-topLeft).cwiseQuotient(graphSize); 48 | } 49 | 50 | void MultiGraph::setXTicks(const VectorXf & ticks, 51 | const vector & labels) 52 | { 53 | if (ticks.size() == (Eigen::DenseIndex)labels.size()) 54 | { 55 | mXTicks = ticks; 56 | mXTickLabels = labels; 57 | } 58 | } 59 | 60 | float MultiGraph::xPosition(float xfrac) const 61 | { 62 | return mPos.x() + hpad + xfrac * (mSize.x() - 2 * hpad); 63 | } 64 | 65 | float MultiGraph::yPosition(float value) const 66 | { 67 | bool hasHeaders = (mLeftHeader.size() + mCenterHeader.size() + mRightHeader.size()) != 0; 68 | bool hasFooters = mXTicks.size() >= 2; 69 | 70 | int bpad = hasFooters ? 12 : 5; 71 | int tpad = hasHeaders ? 15 : 5; 72 | 73 | return mPos.y() + mSize.y() - clamp01(value) * (mSize.y() - tpad - bpad) - bpad; 74 | } 75 | 76 | 77 | void MultiGraph::draw(NVGcontext *ctx) 78 | { 79 | Widget::draw(ctx); 80 | 81 | bool hasFooters = mXTicks.size() >= 2; 82 | 83 | float y0 = yPosition(0.0f); 84 | float y1 = yPosition(1.0f); 85 | float x0 = xPosition(0.0f); 86 | float x1 = xPosition(1.0f); 87 | 88 | nvgStrokeWidth(ctx, 1.0f); 89 | 90 | if (mInWell) 91 | { 92 | // draw a background well 93 | NVGpaint paint = nvgBoxGradient(ctx, mPos.x() + 1, mPos.y() + 1, 94 | mSize.x() - 2, mSize.y() - 2, 3, 4, 95 | Color(0, 32), Color(0, 92)); 96 | nvgBeginPath(ctx); 97 | nvgRoundedRect(ctx, mPos.x(), mPos.y(), mSize.x(), mSize.y(), 2.5); 98 | nvgFillPaint(ctx, paint); 99 | nvgFill(ctx); 100 | } 101 | 102 | if (numPlots() && mValues[0].size() >= 2) 103 | { 104 | nvgSave(ctx); 105 | // Additive blending 106 | nvgGlobalCompositeBlendFunc(ctx, NVGblendFactor::NVG_SRC_ALPHA, NVGblendFactor::NVG_ONE); 107 | 108 | nvgLineJoin(ctx, NVG_BEVEL); 109 | for (int plot = 0; plot < numPlots(); ++plot) 110 | { 111 | const VectorXf &v = mValues[plot]; 112 | if (v.size() < 2) 113 | return; 114 | 115 | float invSize = 1.f / (v.size()-1); 116 | nvgBeginPath(ctx); 117 | if (mFilled) 118 | { 119 | nvgMoveTo(ctx, x0, y0); 120 | nvgLineTo(ctx, x0, yPosition(v[0])); 121 | } 122 | else 123 | nvgMoveTo(ctx, x0, yPosition(v[0])); 124 | 125 | for (int i = 1; i < v.size(); ++i) 126 | nvgLineTo(ctx, xPosition(i*invSize), yPosition(v[i])); 127 | 128 | if (mFilled) 129 | { 130 | nvgLineTo(ctx, x1, y0); 131 | nvgFillColor(ctx, mForegroundColors[plot]); 132 | nvgFill(ctx); 133 | } 134 | Color sColor = mForegroundColors[plot]; 135 | sColor.w() = (sColor.w() + 1.0f) / 2.0f; 136 | nvgStrokeColor(ctx, sColor); 137 | nvgStroke(ctx); 138 | } 139 | 140 | nvgRestore(ctx); 141 | } 142 | 143 | nvgFontFace(ctx, "sans"); 144 | 145 | Color axisColor = Color(0.8f, 0.8f); 146 | 147 | float prevTextBound = 0; 148 | float lastTextBound = 0; 149 | float xPos = 0; 150 | float yPos = 0; 151 | float textWidth = 0.0f; 152 | 153 | if (hasFooters) 154 | { 155 | // draw horizontal axis 156 | nvgBeginPath(ctx); 157 | nvgStrokeColor(ctx, axisColor); 158 | nvgMoveTo(ctx, x0, y0); 159 | nvgLineTo(ctx, x1, y0); 160 | nvgStroke(ctx); 161 | 162 | nvgFontSize(ctx, 9.0f); 163 | nvgTextAlign(ctx, NVG_ALIGN_MIDDLE | NVG_ALIGN_TOP); 164 | nvgFillColor(ctx, axisColor); 165 | 166 | // tick and label at 0 167 | xPos = xPosition(mXTicks[0]); 168 | nvgBeginPath(ctx); 169 | nvgMoveTo(ctx, xPos, y0 - 3); 170 | nvgLineTo(ctx, xPos, y0 + 3); 171 | nvgStroke(ctx); 172 | 173 | textWidth = nvgTextBounds(ctx, 0, 0, mXTickLabels.front().c_str(), nullptr, nullptr); 174 | xPos -= textWidth / 2; 175 | nvgText(ctx, xPos, y0 + 2, mXTickLabels.front().c_str(), NULL); 176 | prevTextBound = xPos + textWidth; 177 | 178 | // tick and label at max 179 | xPos = xPosition(mXTicks[mXTicks.size()-1]); 180 | nvgBeginPath(ctx); 181 | nvgMoveTo(ctx, xPos, y0 - 3); 182 | nvgLineTo(ctx, xPos, y0 + 3); 183 | nvgStroke(ctx); 184 | 185 | textWidth = nvgTextBounds(ctx, 0, 0, mXTickLabels.back().c_str(), nullptr, nullptr); 186 | xPos -= textWidth / 2; 187 | nvgText(ctx, xPos, y0 + 2, mXTickLabels.back().c_str(), NULL); 188 | lastTextBound = xPos; 189 | 190 | int numTicks = mXTicks.size(); 191 | for (int i = 1; i < numTicks; ++i) 192 | { 193 | // tick 194 | xPos = xPosition(mXTicks[i]); 195 | nvgBeginPath(ctx); 196 | nvgMoveTo(ctx, xPos, y0 - 2); 197 | nvgLineTo(ctx, xPos, y0 + 2); 198 | nvgStroke(ctx); 199 | 200 | // tick label 201 | textWidth = nvgTextBounds(ctx, 0, 0, mXTickLabels[i].c_str(), nullptr, nullptr); 202 | xPos -= textWidth / 2; 203 | 204 | // only draw the label if it doesn't overlap with the previous one 205 | // and the last one 206 | if (xPos > prevTextBound + textPad && 207 | xPos + textWidth < lastTextBound - textPad) 208 | { 209 | nvgText(ctx, xPos, y0 + 2, mXTickLabels[i].c_str(), NULL); 210 | prevTextBound = xPos + textWidth; 211 | } 212 | } 213 | } 214 | 215 | if (mYTicks.size() >= 2) 216 | { 217 | // draw vertical axis 218 | nvgBeginPath(ctx); 219 | nvgStrokeColor(ctx, axisColor); 220 | nvgMoveTo(ctx, x0, y0); 221 | nvgLineTo(ctx, x0, y1); 222 | nvgStroke(ctx); 223 | 224 | nvgFillColor(ctx, axisColor); 225 | 226 | int numTicks = mYTicks.size(); 227 | for (int i = 0; i < numTicks; ++i) 228 | { 229 | // tick 230 | yPos = yPosition(mYTicks[i]); 231 | nvgBeginPath(ctx); 232 | int w2 = (i == 0 || i == numTicks-1) ? 3 : 2; 233 | nvgMoveTo(ctx, x0 - w2, yPos); 234 | nvgLineTo(ctx, x0 + w2, yPos); 235 | nvgStroke(ctx); 236 | } 237 | } 238 | 239 | // show the headers 240 | nvgFontSize(ctx, 12.0f); 241 | nvgFillColor(ctx, mTextColor); 242 | 243 | nvgTextAlign(ctx, NVG_ALIGN_LEFT | NVG_ALIGN_TOP); 244 | nvgText(ctx, mPos.x() + 3, mPos.y() + 1, mLeftHeader.c_str(), NULL); 245 | 246 | nvgTextAlign(ctx, NVG_ALIGN_MIDDLE | NVG_ALIGN_TOP); 247 | textWidth = nvgTextBounds(ctx, 0, 0, mCenterHeader.c_str(), nullptr, nullptr); 248 | nvgText(ctx, mPos.x() + mSize.x() / 2 - textWidth / 2, mPos.y() + 1, mCenterHeader.c_str(), NULL); 249 | 250 | nvgTextAlign(ctx, NVG_ALIGN_RIGHT | NVG_ALIGN_TOP); 251 | nvgText(ctx, mPos.x() + mSize.x() - 3, mPos.y() + 1, mRightHeader.c_str(), NULL); 252 | 253 | nvgFontFace(ctx, "sans"); 254 | } 255 | 256 | bool MultiGraph::mouseDragEvent(const Vector2i &p, const Vector2i & /* rel */, 257 | int /* button */, int /* modifiers */) 258 | { 259 | if (!mEnabled) 260 | return false; 261 | 262 | if (mDragCallback) 263 | mDragCallback(graphCoordinateAt(p.cast())); 264 | 265 | return true; 266 | } 267 | 268 | bool MultiGraph::mouseButtonEvent(const Vector2i &p, int /* button */, bool down, int /* modifiers */) 269 | { 270 | if (!mEnabled) 271 | return false; 272 | 273 | if (mDragCallback) 274 | mDragCallback(graphCoordinateAt(p.cast())); 275 | 276 | return true; 277 | } 278 | 279 | void MultiGraph::save(Serializer &s) const 280 | { 281 | Widget::save(s); 282 | s.set("backgroundColor", mBackgroundColor); 283 | s.set("textColor", mTextColor); 284 | s.set("numPlots", (int) mValues.size()); 285 | for (int i = 0; i < (int) mValues.size(); ++i) 286 | { 287 | s.set(std::string("foregroundColor[") + std::to_string(i) + "]", mForegroundColors[i]); 288 | s.set(std::string("values[") + std::to_string(i) + "]", mValues[i]); 289 | } 290 | } 291 | 292 | bool MultiGraph::load(Serializer &s) 293 | { 294 | if (!Widget::load(s)) return false; 295 | if (!s.get("backgroundColor", mBackgroundColor)) return false; 296 | if (!s.get("textColor", mTextColor)) return false; 297 | 298 | int num = 1; 299 | if (!s.get("numPlots", num)) return false; 300 | 301 | mValues.resize(num); 302 | mForegroundColors.resize(num); 303 | for (int i = 0; i < num; ++i) 304 | { 305 | if (!s.get(std::string("foregroundColor[") + std::to_string(i) + "]", mForegroundColors[i])) return false; 306 | if (!s.get(std::string("values[") + std::to_string(i) + "]", mValues[i])) return false; 307 | } 308 | return true; 309 | } -------------------------------------------------------------------------------- /src/HDRImageViewer.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // 4 | // Copyright (C) Wojciech Jarosz . All rights reserved. 5 | // Use of this source code is governed by a BSD-style license that can 6 | // be found in the LICENSE.txt file. 7 | // 8 | 9 | #pragma once 10 | 11 | #include 12 | #include 13 | #include "Fwd.h" 14 | #include "Common.h" 15 | #include "GLImage.h" 16 | #include "ImageShader.h" 17 | 18 | using namespace nanogui; 19 | using namespace Eigen; 20 | 21 | /*! 22 | * @class HDRImageViewer hdrimageviewer.h 23 | * @brief Widget used to manage and display multiple HDR images. 24 | */ 25 | class HDRImageViewer : public Widget 26 | { 27 | public: 28 | HDRImageViewer(Widget * parent, HDRViewScreen * screen); 29 | 30 | void setCurrentImage(ConstImagePtr cur) {m_currentImage = std::move(cur);} 31 | void setReferenceImage(ConstImagePtr ref) {m_referenceImage = std::move(ref);} 32 | 33 | // overridden Widget virtual functions 34 | void draw(NVGcontext* ctx) override; 35 | bool mouseDragEvent(const Vector2i &p, const Vector2i &rel, int button, int modifiers) override; 36 | bool mouseMotionEvent(const Vector2i &p, const Vector2i &rel, int button, int modifiers) override; 37 | bool scrollEvent(const Vector2i &p, const Vector2f &rel) override; 38 | 39 | // Getters and setters 40 | 41 | float scale() const { return m_zoom; } 42 | 43 | const Vector2f& offset() const { return m_offset; } 44 | void setOffset(const Vector2f& offset) { m_offset = offset; } 45 | 46 | float zoomSensitivity() const { return m_zoomSensitivity; } 47 | void setZoomSensitivity(float zoomSensitivity) { m_zoomSensitivity = zoomSensitivity; } 48 | 49 | float gridThreshold() const { return m_gridThreshold; } 50 | void setGridThreshold(float gridThreshold) { m_gridThreshold = gridThreshold; } 51 | 52 | float pixelInfoThreshold() const { return m_pixelInfoThreshold; } 53 | void setPixelInfoThreshold(float pixelInfoThreshold) { m_pixelInfoThreshold = pixelInfoThreshold; } 54 | 55 | /// Function indicating whether the grid is currently visible. 56 | bool gridVisible() const; 57 | 58 | /// Function indicating whether the pixel information is currently visible. 59 | bool pixelInfoVisible() const; 60 | 61 | /// Function indicating whether any of the overlays are visible. 62 | bool helpersVisible() const; 63 | 64 | 65 | // Image transformation functions. 66 | 67 | /// Calculates the image coordinates of the given pixel position on the widget. 68 | Vector2f imageCoordinateAt(const Vector2f& position) const; 69 | 70 | /** 71 | * Calculates the image coordinates of the given pixel position on the widget. 72 | * If the position provided corresponds to a coordinate outside the range of 73 | * the image, the coordinates are clamped to edges of the image. 74 | */ 75 | Vector2f clampedImageCoordinateAt(const Vector2f& position) const; 76 | 77 | /// Calculates the position inside the widget for the given image coordinate. 78 | Vector2f positionForCoordinate(const Vector2f& imageCoordinate) const; 79 | 80 | /// Calculates the position inside the widget for the given image coordinate. 81 | Vector2f screenPositionForCoordinate(const Vector2f& imageCoordinate) const; 82 | 83 | /** 84 | * Modifies the internal state of the image viewer widget so that the pixel at the provided 85 | * position on the widget has the specified image coordinate. Also clamps the values of offset 86 | * to the sides of the widget. 87 | */ 88 | void setImageCoordinateAt(const Vector2f& position, const Vector2f& imageCoordinate); 89 | 90 | /// Centers the image without affecting the scaling factor. 91 | void center(); 92 | 93 | /// Centers and scales the image so that it fits inside the widget. 94 | void fit(); 95 | 96 | /// Moves the offset by the specified amount. Does bound checking. 97 | void moveOffset(const Vector2f& delta); 98 | 99 | /** 100 | * Changes the scale factor by the provided amount modified by the zoom sensitivity member variable. 101 | * The scaling occurs such that the image coordinate under the focused position remains in 102 | * the same position before and after the scaling. 103 | */ 104 | void zoomBy(float amount, const Vector2f &focusPosition); 105 | 106 | /// Zoom in to the next power of two 107 | void zoomIn(); 108 | 109 | /// Zoom out to the previous power of two 110 | void zoomOut(); 111 | 112 | 113 | float zoomLevel() const {return m_zoomLevel;} 114 | void setZoomLevel(float l); 115 | 116 | EChannel channel() {return m_channel;} 117 | void setChannel(EChannel c) {m_channel = c;} 118 | 119 | EBlendMode blendMode() {return m_blendMode;} 120 | void setBlendMode(EBlendMode b) {m_blendMode = b;} 121 | 122 | float gamma() const {return m_gamma;} 123 | void setGamma(float g) {if (m_gamma != g) {m_gamma = g; m_gammaCallback(g);}} 124 | 125 | float exposure() const {return m_exposure;} 126 | void setExposure(float e) {if (m_exposure != e) {m_exposure = e; m_exposureCallback(e);}} 127 | 128 | bool sRGB() const {return m_sRGB;} 129 | void setSRGB(bool b) {m_sRGB = b; m_sRGBCallback(b);} 130 | 131 | bool ditheringOn() const {return m_dither;} 132 | void setDithering(bool b) {m_dither = b;} 133 | 134 | bool drawGridOn() const {return m_drawGrid;} 135 | void setDrawGrid(bool b) {m_drawGrid = b;} 136 | 137 | bool drawValuesOn() const {return m_drawValues;} 138 | void setDrawValues(bool b) {m_drawValues = b;} 139 | 140 | // Callback functions 141 | 142 | /// Callback executed whenever the gamma value has been changed, e.g. via @ref setGamma 143 | const std::function& gammaCallback() const { return m_gammaCallback; } 144 | void setGammaCallback(const std::function &callback) { m_gammaCallback = callback; } 145 | 146 | /// Callback executed whenever the exposure value has been changed, e.g. via @ref setExposure 147 | const std::function& exposureCallback() const { return m_exposureCallback; } 148 | void setExposureCallback(const std::function &callback) { m_exposureCallback = callback; } 149 | 150 | /// Callback executed whenever the sRGB setting has been changed, e.g. via @ref setSRGB 151 | const std::function& sRGBCallback() const { return m_sRGBCallback; } 152 | void setSRGBCallback(const std::function &callback) { m_sRGBCallback = callback; } 153 | 154 | /// Callback executed when the zoom level changes 155 | const std::function& zoomCallback() const { return m_zoomCallback; } 156 | void setZoomCallback(const std::function &callback) { m_zoomCallback = callback; } 157 | 158 | /// Callback executed when mouse hovers over different parts of the image, provides pixel coordinates and values 159 | const std::function pixelHoverCallback() const { return m_pixelHoverCallback; } 160 | void setPixelHoverCallback(const std::function &callback) { m_pixelHoverCallback = callback; } 161 | 162 | private: 163 | Vector2f positionF() const { return mPos.cast(); } 164 | Vector2f sizeF() const { return mSize.cast(); } 165 | Vector2f screenSizeF() const; 166 | 167 | Vector2i imageSize(ConstImagePtr img) const { return img ? img->size() : Vector2i(0,0); } 168 | Vector2f imageSizeF(ConstImagePtr img) const { return imageSize(img).cast(); } 169 | Vector2f scaledImageSizeF(ConstImagePtr img) const { return m_zoom * imageSizeF(img); } 170 | 171 | // Helper drawing methods. 172 | void drawWidgetBorder(NVGcontext* ctx) const; 173 | void drawImageBorder(NVGcontext* ctx) const; 174 | void drawHelpers(NVGcontext* ctx) const; 175 | void drawPixelGrid(NVGcontext* ctx) const; 176 | void drawPixelInfo(NVGcontext *ctx) const; 177 | void imagePositionAndScale(Vector2f & position, Vector2f & scale, 178 | ConstImagePtr image); 179 | 180 | Vector2f centerOffset(ConstImagePtr img) const; 181 | 182 | ImageShader m_shader; 183 | 184 | HDRViewScreen * m_screen = nullptr; 185 | ConstImagePtr m_currentImage = nullptr; 186 | ConstImagePtr m_referenceImage = nullptr; 187 | float m_exposure = 0.f, 188 | m_gamma = 2.2f; 189 | bool m_sRGB = true, 190 | m_dither = true, 191 | m_drawGrid = true, 192 | m_drawValues = true; 193 | 194 | 195 | // Image display parameters. 196 | float m_zoom; ///< The scale/zoom of the image 197 | float m_zoomLevel; ///< The zoom level 198 | Vector2f m_offset; ///< The panning offset of the 199 | EChannel m_channel = EChannel::RGB; ///< Which channel to display 200 | EBlendMode m_blendMode = EBlendMode::NORMAL_BLEND; ///< How to blend the current and reference images 201 | 202 | // Fine-tuning parameters. 203 | float m_zoomSensitivity = 1.0717734625f; 204 | 205 | // Image info parameters. 206 | float m_gridThreshold = -1; 207 | float m_pixelInfoThreshold = -1; 208 | 209 | // various callback functions 210 | std::function m_exposureCallback; 211 | std::function m_gammaCallback; 212 | std::function m_sRGBCallback; 213 | std::function m_zoomCallback; 214 | std::function m_pixelHoverCallback; 215 | 216 | public: 217 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW 218 | }; -------------------------------------------------------------------------------- /src/EnvMap.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 "EnvMap.h" 9 | 10 | using namespace Eigen; 11 | using std::vector; 12 | using std::string; 13 | 14 | Vector2f convertEnvMappingUV(EEnvMappingUVMode dst, EEnvMappingUVMode src, const Vector2f & srcUV) 15 | { 16 | Vector2f uv; 17 | Vector3f xyz; 18 | 19 | switch (src) 20 | { 21 | case ANGULAR_MAP: 22 | xyz = angularMapToXYZ(srcUV); 23 | break; 24 | case MIRROR_BALL: 25 | xyz = mirrorBallToXYZ(srcUV); 26 | break; 27 | case LAT_LONG: 28 | xyz = latLongToXYZ(srcUV); 29 | break; 30 | case CYLINDRICAL: 31 | xyz = cylindricalToXYZ(srcUV); 32 | break; 33 | case CUBE_MAP: 34 | xyz = cubeMapToXYZ(srcUV); 35 | break; 36 | } 37 | 38 | switch (dst) 39 | { 40 | case ANGULAR_MAP: 41 | uv = XYZToAngularMap(xyz); 42 | break; 43 | case MIRROR_BALL: 44 | uv = XYZToMirrorBall(xyz); 45 | break; 46 | case LAT_LONG: 47 | uv = XYZToLatLong(xyz); 48 | break; 49 | case CYLINDRICAL: 50 | uv = XYZToCylindrical(xyz); 51 | break; 52 | case CUBE_MAP: 53 | uv = XYZToCubeMap(xyz); 54 | break; 55 | } 56 | 57 | return uv; 58 | } 59 | 60 | 61 | const vector & envMappingNames() 62 | { 63 | static const vector names = 64 | { 65 | "Angular map", 66 | "Mirror ball", 67 | "Longitude-latitude", 68 | "Cylindrical", 69 | "Cube map" 70 | }; 71 | return names; 72 | } 73 | 74 | UV2XYZFn * envMapUVToXYZ(EEnvMappingUVMode mode) 75 | { 76 | switch (mode) 77 | { 78 | case ANGULAR_MAP: 79 | return angularMapToXYZ; 80 | case MIRROR_BALL: 81 | return mirrorBallToXYZ; 82 | case LAT_LONG: 83 | return latLongToXYZ; 84 | case CYLINDRICAL: 85 | return cylindricalToXYZ; 86 | case CUBE_MAP: 87 | return cubeMapToXYZ; 88 | } 89 | } 90 | 91 | XYZ2UVFn * XYZToEnvMapUV(EEnvMappingUVMode mode) 92 | { 93 | switch (mode) 94 | { 95 | case ANGULAR_MAP: 96 | return XYZToAngularMap; 97 | case MIRROR_BALL: 98 | return XYZToMirrorBall; 99 | case LAT_LONG: 100 | return XYZToLatLong; 101 | case CYLINDRICAL: 102 | return XYZToCylindrical; 103 | case CUBE_MAP: 104 | return XYZToCubeMap; 105 | } 106 | } 107 | 108 | Vector3f angularMapToXYZ(const Vector2f& UV) 109 | { 110 | // image plane coordinates going from (-1,1) for x and y 111 | // with center of image being (0,0) 112 | Vector2f XY = 2*UV - Vector2f::Ones(); 113 | 114 | // phi varies linearly with the radius from center 115 | float phi = clamp(XY.norm() * M_PI, 0.0, M_PI); 116 | float theta = std::atan2(XY(1),XY(0)); 117 | 118 | float sinPhi = std::sin(phi); 119 | return Vector3f( sinPhi*std::cos(theta), 120 | -sinPhi*std::sin(theta), 121 | std::cos(phi)); 122 | } 123 | 124 | Vector3f mirrorBallToXYZ(const Vector2f& UV) 125 | { 126 | // image plane coordinates going from (-1,1) for x and y 127 | // with center of image being (0,0) 128 | Vector2f XY = 2*UV - Vector2f::Ones(); 129 | 130 | // sin(phi) varies linearly with the radius from center 131 | float phi = 2*std::asin(clamp(XY.norm(), 0.0f, 1.0f)); 132 | float theta = std::atan2(XY(1),XY(0)); 133 | 134 | float sinPhi = std::sin(phi); 135 | return Vector3f( sinPhi*std::cos(theta), 136 | -sinPhi*std::sin(theta), 137 | std::cos(phi)); 138 | } 139 | 140 | Vector3f latLongToXYZ(const Vector2f& UV) 141 | { 142 | // theta varies linearly with U, 143 | // and phi varies linearly with V 144 | float theta = lerp(1.5f*M_PI, -M_PI_2, UV(0)); 145 | float phi = UV(1)*M_PI; 146 | 147 | float sinPhi = std::sin(phi); 148 | return Vector3f( sinPhi*std::cos(theta), 149 | std::cos(phi), 150 | sinPhi*std::sin(theta)); 151 | } 152 | 153 | 154 | Vector3f cylindricalToXYZ(const Vector2f& UV) 155 | { 156 | // theta varies linearly with U, 157 | // and y=cosPhi varies linearly with V 158 | float theta = lerp(1.5f*M_PI, -M_PI_2, UV(0)); 159 | float cosPhi = lerp(1.f, -1.f, UV(1)); 160 | 161 | float sinPhi = std::sqrt(1.f-cosPhi*cosPhi); 162 | return Vector3f( sinPhi*std::cos(theta), 163 | cosPhi, 164 | sinPhi*std::sin(theta)); 165 | } 166 | 167 | Vector3f cubeMapToXYZ(const Vector2f& UV) 168 | { 169 | // This is assuming that the Cubemap is a vertical cross 170 | Vector3f xyz; 171 | float k, j; 172 | 173 | if (clamp(UV(0), (1.0f/3.0f), (2.0f/3.0f)) == UV(0)) 174 | { 175 | j = clamp(UV(0), (1.0f/3.0f), (2.0f/3.0f)); 176 | xyz(0) = (UV(0) - 0.5f) * 6.0f; 177 | if (clamp(UV(1), 0.0f, 0.25f) == UV(1)) 178 | { 179 | xyz(1) = 1; 180 | xyz(2) = (UV(1) - 0.125f) * 8.0f; 181 | } 182 | else if (clamp(UV(1), 0.25f, 0.5f) == UV(1)) 183 | { 184 | xyz(1) = (0.375f - UV(1)) * 8.0f; 185 | xyz(2) = 1; 186 | } 187 | else if (clamp(UV(1), 0.5f, 0.75f) == UV(1)) 188 | { 189 | xyz(1) = -1; 190 | xyz(2) = (0.625f - UV(1)) * 8.0f; 191 | } 192 | else 193 | { 194 | xyz(1) = (UV(1) - 0.875f) * 8.0f; 195 | xyz(2) = -1; 196 | } 197 | } 198 | else if (clamp(UV(0), 0.0f, (1.0f/3.0f)) == UV(0)) 199 | { 200 | xyz(0) = -1; 201 | k = clamp(UV(1), 0.25f, 0.5f); 202 | j = clamp(UV(0), 0.0f, (1.0f/3.0f)); 203 | xyz(1) = (0.375f - k) * 8.0f; 204 | xyz(2) = (j - (1.0f/6.0f)) * 6.0f; 205 | } 206 | else 207 | { 208 | xyz(0) = 1; 209 | k = clamp(UV(1), 0.25f, 0.5f); 210 | j = clamp(UV(0), (2.0f/3.0f), 1.0f); 211 | xyz(1) = (0.375f - k) * 8.0f; 212 | xyz(2) = ((5.0f/6.0f) - j) * 6.0f; 213 | } 214 | return xyz.normalized(); 215 | } 216 | 217 | //////////////////////////////// 218 | 219 | Vector2f XYZToAngularMap(const Vector3f& xyz) 220 | { 221 | float phi = std::acos(xyz(2)); 222 | float theta = std::atan2(xyz(1), xyz(0)); 223 | 224 | float U = (phi/M_PI)*std::cos(theta); 225 | float V = -(phi/M_PI)*std::sin(theta); 226 | 227 | return Vector2f(0.5f*(U + 1.0f), 0.5f*(V + 1.0f)); 228 | } 229 | 230 | Vector2f XYZToMirrorBall(const Vector3f& xyz) 231 | { 232 | float phi = std::acos(xyz(2)); 233 | float theta = std::atan2(xyz(1),xyz(0)); 234 | 235 | float sinPhi2 = std::sin(phi/2.0f); 236 | return Vector2f(0.5f*( sinPhi2*std::cos(theta) + 1.0f), 237 | 0.5f*(-sinPhi2*std::sin(theta) + 1.0f)); 238 | } 239 | 240 | 241 | Vector2f XYZToLatLong(const Vector3f& xyz) 242 | { 243 | // theta varies linearly with U, 244 | // and phi varies linearly with V 245 | float phi = std::acos(xyz(1)); 246 | float theta = std::atan2(xyz(2), xyz(0)); 247 | 248 | return Vector2f(mod(lerpFactor(1.5f*M_PI, -M_PI_2, theta), 1.0f), 249 | phi/M_PI); 250 | } 251 | 252 | 253 | Vector2f XYZToCylindrical(const Vector3f& xyz) 254 | { 255 | // U varies linearly with theta, 256 | // and V varies linearly with y=cosPhi 257 | float theta = std::atan2(xyz(2), xyz(0)); 258 | return Vector2f(mod(lerpFactor(1.5f*M_PI, -M_PI_2, theta), 1.0f), 259 | lerpFactor(1.f, -1.f, xyz(1))); 260 | } 261 | 262 | 263 | Vector2f XYZToCubeMap(const Vector3f& xyz) 264 | { 265 | // Again, the CubeMap is a vertical cross 266 | float U, V; 267 | float l; // infinite-norm of xyz 268 | int flg; // Marker of which window we're in 269 | Vector3f temp; 270 | 271 | // Make sure that the infinite norm of xyz == 1; 272 | // flg determines which side we're looking at. 273 | l = fabs(xyz(0)); 274 | flg = (int)sign(xyz(0)); 275 | if (fabs(xyz(1)) > l) 276 | { 277 | l = fabs(xyz(1)); 278 | flg = (int)sign(xyz(1)) * 2; 279 | } 280 | if (fabs(xyz(2)) > l) 281 | { 282 | l = fabs(xyz(2)); 283 | flg = (int)sign(xyz(2)) * 3; 284 | } 285 | l = 1 / l; 286 | temp = l * xyz; 287 | 288 | if (flg == 3) 289 | { 290 | U = temp(0) / 6.0f + 0.5f; 291 | V = -temp(1) / 8.0f + 0.375f; 292 | } 293 | else if (flg == -1) 294 | { 295 | U = temp(2) / 6.0f + (1.0f/6.0f); // stupid type promotion oddity 296 | V = -temp(1) / 8.0f + 0.375f; // with Viz Studio so this explicit 297 | } // float notation crap has to be done. 298 | else if (flg == 1) 299 | { 300 | U = -temp(2) / 6.0f + (5.0f/6.0f); // I actually had a 180-degree out of 301 | V = -temp(1) / 8.0f + 0.375f; // phase thing going w/o the explicit 302 | } // float notation and parenthesis. 303 | else if (flg == 2) 304 | { 305 | U = temp(0) / 6.0f + 0.5f; 306 | V = temp(2) / 8.0f + 0.125f; 307 | } 308 | else if (flg == -2) 309 | { 310 | U = temp(0) / 6.0f + 0.5f; 311 | V = -temp(2) / 8.0f + 0.625f; 312 | } 313 | else 314 | { 315 | U = temp(0) / 6.0f + 0.5f; 316 | V = temp(1) / 8.0f + 0.875f; 317 | } 318 | 319 | return Vector2f(U, V); 320 | } 321 | -------------------------------------------------------------------------------- /src/ImageButton.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 | // This file is adapted from tev: 7 | // This file was developed by Thomas Müller . 8 | // It is published under the BSD 3-Clause License within the LICENSE file. 9 | 10 | 11 | #include "ImageButton.h" 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | using namespace nanogui; 19 | using namespace std; 20 | 21 | ImageButton::ImageButton(Widget *parent, const string &caption) 22 | : Widget (parent), m_caption(caption) 23 | { 24 | mFontSize = 15; 25 | } 26 | 27 | void ImageButton::recomputeStringClipping() 28 | { 29 | m_cutoff = 0; 30 | m_sizeForWhichCutoffWasComputed = Eigen::Vector2i::Constant(0); 31 | } 32 | 33 | Vector2i ImageButton::preferredSize(NVGcontext *ctx) const 34 | { 35 | // calculate size of the image iD number 36 | nvgFontFace(ctx, "sans-bold"); 37 | nvgFontSize(ctx, mFontSize); 38 | string idString = to_string(m_id); 39 | float idSize = nvgTextBounds(ctx, 0, 0, idString.c_str(), nullptr, nullptr); 40 | 41 | // calculate space for isModified icon 42 | nvgFontFace(ctx, "icons"); 43 | nvgFontSize(ctx, mFontSize * 1.5f); 44 | float iw = nvgTextBounds(ctx, 0, 0, utf8(ENTYPO_ICON_PENCIL).data(), nullptr, nullptr); 45 | 46 | // calculate size of the filename 47 | nvgFontFace(ctx, "sans"); 48 | nvgFontSize(ctx, mFontSize); 49 | float tw = nvgTextBounds(ctx, 0, 0, m_caption.c_str(), nullptr, nullptr); 50 | 51 | return Vector2i(static_cast(tw + iw + idSize) + 15, mFontSize + 6); 52 | } 53 | 54 | bool ImageButton::mouseButtonEvent(const Vector2i &p, int button, bool down, int modifiers) 55 | { 56 | Widget::mouseButtonEvent(p, button, down, modifiers); 57 | 58 | if (!mEnabled || !down) 59 | { 60 | return false; 61 | } 62 | 63 | if (button == GLFW_MOUSE_BUTTON_2 || 64 | (button == GLFW_MOUSE_BUTTON_1 && modifiers & GLFW_MOD_SHIFT)) 65 | { 66 | // If we already were the reference, then let's disable using us a reference. 67 | m_isReference = !m_isReference; 68 | 69 | // If we newly became the reference, then we need to disable the existing reference 70 | // if it exists. 71 | if (m_isReference) 72 | { 73 | for (auto widget : parent()->children()) 74 | { 75 | ImageButton* b = dynamic_cast(widget); 76 | if (b && b != this) 77 | b->m_isReference = false; 78 | } 79 | } 80 | 81 | // Invoke the callback in any case, such that the surrounding code can 82 | // react to new references or a loss of a reference image. 83 | if (m_referenceCallback) 84 | m_referenceCallback(m_isReference ? m_id : -1); 85 | 86 | return true; 87 | } 88 | else if (button == GLFW_MOUSE_BUTTON_1) 89 | { 90 | if (!m_isSelected) 91 | { 92 | // Unselect the other, currently selected image. 93 | for (auto widget : parent()->children()) 94 | { 95 | ImageButton *b = dynamic_cast(widget); 96 | if (b && b != this) 97 | b->m_isSelected = false; 98 | } 99 | 100 | m_isSelected = true; 101 | if (m_selectedCallback) 102 | m_selectedCallback(m_id); 103 | } 104 | return true; 105 | } 106 | 107 | return false; 108 | } 109 | 110 | 111 | string ImageButton::highlighted() const 112 | { 113 | vector pieces; 114 | if (m_highlightBegin <= 0) 115 | { 116 | if (m_highlightEnd <= 0) 117 | pieces.emplace_back(m_caption); 118 | else 119 | { 120 | size_t offset = m_highlightEnd; 121 | pieces.emplace_back(m_caption.substr(offset)); 122 | pieces.emplace_back(m_caption.substr(0, offset)); 123 | } 124 | } 125 | else 126 | { 127 | size_t beginOffset = m_highlightBegin; 128 | size_t endOffset = m_highlightEnd; 129 | pieces.emplace_back(m_caption.substr(endOffset)); 130 | pieces.emplace_back(m_caption.substr(beginOffset, endOffset - beginOffset)); 131 | pieces.emplace_back(m_caption.substr(0, beginOffset)); 132 | } 133 | 134 | return pieces.size() > 1 ? pieces[1] : ""; 135 | } 136 | 137 | 138 | static float triangleWave(float t, float period = 1.f) 139 | { 140 | float a = period/2.f; 141 | return fabs(2 * (t/a - floor(t/a + 0.5f))); 142 | } 143 | 144 | void ImageButton::draw(NVGcontext *ctx) 145 | { 146 | Widget::draw(ctx); 147 | 148 | int extraBorder = 0; 149 | if (m_isReference) 150 | { 151 | extraBorder = 2; 152 | nvgBeginPath(ctx); 153 | nvgRoundedRect(ctx, mPos.x(), mPos.y(), mSize.x(), mSize.y(), 3+1); 154 | nvgFillColor(ctx, Color(0.7f, 0.4f, 0.4f, 1.0f)); 155 | nvgFill(ctx); 156 | } 157 | 158 | // Fill the button with color. 159 | if (m_isSelected || mMouseFocus) 160 | { 161 | nvgBeginPath(ctx); 162 | nvgRoundedRect(ctx, mPos.x() + extraBorder, mPos.y() + extraBorder, 163 | mSize.x() - 2*extraBorder, mSize.y() - 2*extraBorder, 3); 164 | nvgFillColor(ctx, m_isSelected ? mTheme->mButtonGradientBotPushed : mTheme->mBorderMedium); 165 | nvgFill(ctx); 166 | } 167 | 168 | // percent progress bar 169 | if (m_progress >= 0.f && m_progress < 1.f) 170 | { 171 | int barPos = (int) std::round((mSize.x() - 4) * m_progress); 172 | 173 | auto paint = nvgBoxGradient( 174 | ctx, mPos.x() + 2 - 1, mPos.y() + 2 -1, 175 | barPos + 1.5f, mSize.y() - 2*extraBorder + 1, 3, 4, 176 | Color(.14f, .31f, .5f, .95f), Color(.045f, .05f, .141f, .95f)); 177 | 178 | nvgBeginPath(ctx); 179 | nvgRoundedRect(ctx, mPos.x() + 2, mPos.y() + 2, 180 | barPos, mSize.y() - 2*2, 3); 181 | nvgFillPaint(ctx, paint); 182 | nvgFill(ctx); 183 | } 184 | // busy progress bar 185 | else if (m_progress < 0.f) 186 | { 187 | int leftEdge = mPos.x() + 2; 188 | float time = glfwGetTime(); 189 | float anim1 = smoothStep(0.0f, 1.0f, smoothStep(0.0f, 1.0f, smoothStep(0.0f, 1.0f, triangleWave(time/4.f)))); 190 | float anim2 = smoothStep(0.0f, 1.0f, triangleWave(time/4.f*2.f)); 191 | 192 | int barSize = (int) std::round(lerp(float(mSize.x() - 4) * 0.05f, float(mSize.x() - 4) * 0.25f, anim2)); 193 | int left = (int) std::round(lerp((float)leftEdge, float(mSize.x() - 2 - barSize), anim1)); 194 | 195 | auto paint = nvgBoxGradient( 196 | ctx, left - 1, mPos.y() + 2 -1, 197 | barSize + 1.5f, mSize.y() - 2*extraBorder + 1, 3, 4, 198 | Color(.14f, .31f, .5f, .95f), Color(.045f, .05f, .141f, .95f)); 199 | 200 | nvgBeginPath(ctx); 201 | nvgRoundedRect(ctx, left, mPos.y() + 2, 202 | barSize, mSize.y() - 2*2, 3); 203 | nvgFillPaint(ctx, paint); 204 | nvgFill(ctx); 205 | } 206 | 207 | nvgFontSize(ctx, mFontSize); 208 | nvgFontFace(ctx, "sans-bold"); 209 | string idString = to_string(m_id); 210 | float idSize = nvgTextBounds(ctx, 0, 0, idString.c_str(), nullptr, nullptr); 211 | 212 | nvgFontSize(ctx, mFontSize * 1.5f); 213 | nvgFontFace(ctx, "icons"); 214 | float iconSize = nvgTextBounds(ctx, 0, 0, utf8(ENTYPO_ICON_PENCIL).data(), nullptr, nullptr); 215 | 216 | nvgFontSize(ctx, mFontSize); 217 | nvgFontFace(ctx, m_isSelected ? "sans-bold" : "sans"); 218 | 219 | // trim caption to available space 220 | if (mSize.x() == preferredSize(ctx).x()) 221 | m_cutoff = 0; 222 | else if (mSize != m_sizeForWhichCutoffWasComputed) 223 | { 224 | m_cutoff = 0; 225 | while (nvgTextBounds(ctx, 0, 0, m_caption.substr(m_cutoff).c_str(), nullptr, nullptr) > mSize.x() - 15 - idSize - iconSize) 226 | ++m_cutoff; 227 | 228 | m_sizeForWhichCutoffWasComputed = mSize; 229 | } 230 | 231 | // Image name 232 | string trimmedCaption = m_caption.substr(m_cutoff); 233 | 234 | 235 | vector pieces; 236 | if (m_highlightBegin <= m_cutoff) 237 | { 238 | if (m_highlightEnd <= m_cutoff) 239 | pieces.emplace_back(trimmedCaption); 240 | else 241 | { 242 | size_t offset = m_highlightEnd - m_cutoff; 243 | pieces.emplace_back(trimmedCaption.substr(offset)); 244 | pieces.emplace_back(trimmedCaption.substr(0, offset)); 245 | } 246 | } 247 | else 248 | { 249 | size_t beginOffset = m_highlightBegin - m_cutoff; 250 | size_t endOffset = m_highlightEnd - m_cutoff; 251 | pieces.emplace_back(trimmedCaption.substr(endOffset)); 252 | pieces.emplace_back(trimmedCaption.substr(beginOffset, endOffset - beginOffset)); 253 | pieces.emplace_back(trimmedCaption.substr(0, beginOffset)); 254 | } 255 | 256 | if (m_cutoff > 0 && m_cutoff < m_caption.size()) 257 | pieces.back() = string{"…"} + pieces.back(); 258 | 259 | Vector2f center = mPos.cast() + mSize.cast() * 0.5f; 260 | Vector2f bottomRight = mPos.cast() + mSize.cast(); 261 | Vector2f textPos(bottomRight.x() - 5, center.y()); 262 | NVGcolor regularTextColor = (m_isSelected || m_isReference || mMouseFocus) ? mTheme->mTextColor : Color(190, 100); 263 | NVGcolor hightlightedTextColor = Color(190, 255); 264 | 265 | nvgFontSize(ctx, mFontSize); 266 | nvgTextAlign(ctx, NVG_ALIGN_RIGHT | NVG_ALIGN_MIDDLE); 267 | 268 | for (size_t i = 0; i < pieces.size(); ++i) 269 | { 270 | nvgFontFace(ctx, i == 1 ? "sans-bold" : "sans"); 271 | nvgFillColor(ctx, i == 1 ? hightlightedTextColor : regularTextColor); 272 | nvgText(ctx, textPos.x(), textPos.y(), pieces[i].c_str(), nullptr); 273 | textPos.x() -= nvgTextBounds(ctx, 0, 0, pieces[i].c_str(), nullptr, nullptr); 274 | } 275 | 276 | // isModified icon 277 | auto icon = utf8(m_isModified ? ENTYPO_ICON_PENCIL : ENTYPO_ICON_SAVE); 278 | nvgFontSize(ctx, mFontSize * 0.8f); 279 | nvgFontFace(ctx, "icons"); 280 | nvgFillColor(ctx, mTheme->mTextColor); 281 | nvgTextAlign(ctx, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE); 282 | nvgText(ctx, mPos.x() + 5, textPos.y(), icon.data(), nullptr); 283 | 284 | // Image number 285 | nvgFontSize(ctx, mFontSize); 286 | nvgFontFace(ctx, "sans-bold"); 287 | nvgTextAlign(ctx, NVG_ALIGN_LEFT | NVG_ALIGN_MIDDLE); 288 | nvgFillColor(ctx, mTheme->mTextColor); 289 | nvgText(ctx, mPos.x() + 20, textPos.y(), idString.c_str(), nullptr); 290 | } 291 | 292 | 293 | void ImageButton::setHighlightRange(size_t begin, size_t end) 294 | { 295 | size_t beginIndex = begin; 296 | if (end > m_caption.size()) 297 | { 298 | throw std::invalid_argument{fmt::format( 299 | "end ({:d}) must not be larger than m_caption.size() ({:d})", 300 | end, m_caption.size())}; 301 | } 302 | 303 | m_highlightBegin = beginIndex; 304 | m_highlightEnd = max(m_caption.size() - end, beginIndex); 305 | 306 | if (m_highlightBegin == m_highlightEnd || m_caption.empty()) 307 | return; 308 | 309 | // Extend beginning and ending of highlighted region to entire word/number 310 | if (isalnum(m_caption[m_highlightBegin])) 311 | while (m_highlightBegin > 0 && isalnum(m_caption[m_highlightBegin - 1])) 312 | --m_highlightBegin; 313 | 314 | if (isalnum(m_caption[m_highlightEnd - 1])) 315 | while (m_highlightEnd < m_caption.size() && isalnum(m_caption[m_highlightEnd])) 316 | ++m_highlightEnd; 317 | } 318 | -------------------------------------------------------------------------------- /src/Common.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 | #if defined(_MSC_VER) 10 | // Make MS cmath define M_PI 11 | #define _USE_MATH_DEFINES 12 | #endif 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include "Fwd.h" 20 | 21 | 22 | // Also define control key for windows/mac/linux 23 | #if defined(__APPLE__) || defined(DOXYGEN_DOCUMENTATION_BUILD) 24 | /// If on OSX, maps to ``GLFW_MOD_CONTROL``. Otherwise, maps to ``GLFW_MOD_SUPER``. 25 | #define SYSTEM_CONTROL_MOD GLFW_MOD_CONTROL 26 | #else 27 | #define SYSTEM_CONTROL_MOD GLFW_MOD_SUPER 28 | #endif 29 | 30 | 31 | template 32 | inline T sign(T a) {return (a > 0) ? T (1) : (a < 0) ? T (-1) : 0;} 33 | 34 | /*! 35 | * @brief Clamps a double between two bounds. 36 | * 37 | * This function has been specially crafted to prevent NaNs from propagating. 38 | * 39 | * @param a The value to clamp. 40 | * @param l The lower bound. 41 | * @param h The upper bound. 42 | * @return The value \a a clamped to the lower and upper bounds. 43 | */ 44 | template 45 | inline T clamp(T a, T l, T h) 46 | { 47 | return (a >= l) ? ((a <= h) ? a : h) : l; 48 | } 49 | 50 | 51 | template 52 | inline T clamp01(T a) 53 | { 54 | return clamp(a, T(0), T(1)); 55 | } 56 | 57 | /*! 58 | * @brief Linear interpolation. 59 | * 60 | * Linearly interpolates between \a a and \a b, using parameter t. 61 | * 62 | * @param a A value. 63 | * @param b Another value. 64 | * @param t A blending factor of \a a and \a b. 65 | * @return Linear interpolation of \a a and \b - 66 | * a value between a and b if \a t is between 0 and 1. 67 | */ 68 | template 69 | inline T lerp(T a, T b, S t) 70 | { 71 | return T((S(1)-t) * a + t * b); 72 | } 73 | 74 | 75 | /*! 76 | * @brief Inverse linear interpolation. 77 | * 78 | * Given three values \a a, \a b, \a m, determines the parameter value 79 | * \a t, such that m = lerp(a,b,lerpFactor(a,b,m)) 80 | * 81 | * @param a The start point 82 | * @param b The end point 83 | * @param m A third point (typically between \a a and \a b) 84 | * @return The interpolation factor \a t such that m = lerp(a,b,lerpFactor(a,b,m)) 85 | */ 86 | template 87 | inline T lerpFactor(T a, T b, T m) 88 | { 89 | return (m - a) / (b - a); 90 | } 91 | 92 | 93 | /*! 94 | * @brief Smoothly interpolates between 0 and 1 as x moves between a and b. 95 | * 96 | * Does a smooth s-curve (Hermite) interpolation between two values. 97 | * 98 | * @param a A value. 99 | * @param b Another value. 100 | * @param x A number between \a a and \a b. 101 | * @return A value between 0.0 and 1.0. 102 | */ 103 | template 104 | inline T smoothStep(T a, T b, T x) 105 | { 106 | T t = clamp(lerpFactor(a,b,x), T(0), T(1)); 107 | return t*t*(T(3) - T(2)*t); 108 | } 109 | 110 | /*! 111 | * @brief Smoothly interpolates between 0 and 1 as x moves between a and b. 112 | * 113 | * Does a smooth s-curve interpolation between two values using the 114 | * 6th-order polynomial proposed by Perlin. 115 | * 116 | * @param a A value. 117 | * @param b Another value. 118 | * @param x A number between \a a and \a b. 119 | * @return A value between 0.0 and 1.0. 120 | */ 121 | template 122 | inline T smootherStep(T a, T b, T x) 123 | { 124 | T t = clamp(lerpFactor(a,b,x), T(0), T(1)); 125 | return t*t*t*(t*(t*T(6) - T(15)) + T(10)); 126 | } 127 | 128 | /*! 129 | * @brief Cosine interpolation between between 0 and 1 as x moves between a and b. 130 | * 131 | * @param a A value. 132 | * @param b Another value. 133 | * @param x A number between \a a and \a b. 134 | * @return A value between 0.0 and 1.0. 135 | */ 136 | template 137 | inline T cosStep(T a, T b, T x) 138 | { 139 | T t = clamp(lerpFactor(a,b,x), T(0), T(1)); 140 | return T(0.5)*(T(1)-cos(t*T(M_PI))); 141 | } 142 | 143 | //! The inverse of the cosStep function. 144 | template 145 | inline T inverseCosStep(T a, T b, T x) 146 | { 147 | T t = clamp(lerpFactor(a,b,x), T(0), T(1)); 148 | return acos(T(1) - T(2)*t)*T(M_1_PI); 149 | } 150 | 151 | 152 | /*! 153 | * @brief Evaluates Perlin's bias function to control the mean/midpoint of a function. 154 | * 155 | * Remaps the value t to increase/decrease the midpoint while preserving the values at t=0 and t=1. 156 | * 157 | * As described in: 158 | * "Hypertexture" 159 | * Ken Perlin and Eric M. Hoffert: Computer Graphics, v23, n3, p287-296, 1989. 160 | * 161 | * Properties: 162 | * bias(0.0, a) = 0, 163 | * bias(0.5, a) = a, 164 | * bias(1.0, a) = 1, and 165 | * bias(t , a) remaps the value t using a power curve. 166 | * 167 | * @tparam T The template parameter (typically float or double) 168 | * @param t The percentage value in [0,1] 169 | * @param a The shape parameter in [0,1] 170 | * @return The remapped result in [0,1] 171 | */ 172 | template 173 | inline T biasPerlin(T t, T a) 174 | { 175 | return pow(t, -log2(a)); 176 | } 177 | 178 | /*! 179 | * @brief Perlin's gain function to increase/decrease the gradient/slope of the input at the midpoint. 180 | * 181 | * Remaps the value t to increase or decrease contrast using an s-curve (or inverse s-curve) function. 182 | * 183 | * As described in: 184 | * "Hypertexture" 185 | * Ken Perlin and Eric M. Hoffert: Computer Graphics, v23, n3, p287-296, 1989. 186 | * 187 | * Properties: 188 | * gain(0.0, P) = 0.0, 189 | * gain(0.5, P) = 0.5, 190 | * gain(1.0, P) = 1.0, 191 | * gain(t , 1) = t. 192 | * gain(gain(t, P, 1/P) = t. 193 | * 194 | * @tparam T The template parameter (typically float or double) 195 | * @param t The percentage value in [0,1] 196 | * @param P The shape exponent. In Perlin's original version the exponent P = -log2(a). 197 | * In this version we pass the exponent directly to avoid the logarithm. 198 | * P > 1 creates an s-curve, and P < 1 an inverse s-curve. 199 | * If the input is a linear ramp, the slope of the output at the midpoint 0.5 becomes P. 200 | * @return The remapped result in [0,1] 201 | */ 202 | template 203 | inline T gainPerlin(T t, T P) 204 | { 205 | if (t > T(0.5)) 206 | return T(1) - T(0.5)*pow(T(2) - T(2)*t, P); 207 | else 208 | return T(0.5)*pow(T(2)*t, P); 209 | } 210 | 211 | /*! 212 | * @brief Evaluates Schlick's rational version of Perlin's bias function. 213 | * 214 | * As described in: 215 | * "Fast Alternatives to Perlin's Bias and Gain Functions" 216 | * Christophe Schlick: Graphics Gems IV, p379-382, April 1994. 217 | * 218 | * @tparam T The template parameter (typically float or double) 219 | * @param t The percentage value (between 0 and 1) 220 | * @param a The shape parameter (between 0 and 1) 221 | * @return The remapped result 222 | */ 223 | template 224 | inline T biasSchlick(T t, T a) 225 | { 226 | return t / ((((T(1)/a) - T(2)) * (T(1) - t)) + T(1)); 227 | } 228 | 229 | /*! 230 | * @brief Evaluates Schlick's rational version of Perlin's gain function. 231 | * 232 | * As described in: 233 | * "Fast Alternatives to Perlin's Bias and Gain Functions" 234 | * Christophe Schlick: Graphics Gems IV, p379-382, April 1994. 235 | * 236 | * @tparam T The template parameter (typically float or double) 237 | * @param t The percentage value (between 0 and 1) 238 | * @param a The shape parameter (between 0 and 1) 239 | * @return The remapped result 240 | */ 241 | template 242 | inline T gainSchlick(T t, T a) 243 | { 244 | if (t < T(0.5)) 245 | return biasSchlick(t * T(2), a)/T(2); 246 | else 247 | return biasSchlick(t * T(2) - T(1), T(1) - a)/T(2) + T(0.5); 248 | } 249 | 250 | template 251 | inline T brightnessContrastL(T v, T slope, T midpoint) 252 | { 253 | return (v - midpoint) * slope + T(0.5); 254 | } 255 | 256 | template 257 | inline T brightnessContrastNL(T v, T slope, T bias) 258 | { 259 | return gainPerlin(biasSchlick(clamp01(v), bias), slope); 260 | } 261 | 262 | 263 | //! Returns a modulus b. 264 | template 265 | inline T mod(T a, T b) 266 | { 267 | int n = (int)(a/b); 268 | a -= n*b; 269 | if (a < 0) 270 | a += b; 271 | return a; 272 | } 273 | 274 | 275 | template 276 | inline T logScale(T val) 277 | { 278 | static const T eps = T(0.001); 279 | static const T logeps = std::log(eps); 280 | 281 | return val > 0 ? (std::log(val + eps) - logeps) : -(std::log(-val + eps) - logeps); 282 | } 283 | 284 | 285 | template 286 | inline T normalizedLogScale(T val, T minLog, T diffLog) 287 | { 288 | return (logScale(val) - minLog) / diffLog; 289 | } 290 | 291 | 292 | template 293 | inline T normalizedLogScale(T val) 294 | { 295 | static const T minLog = logScale(T(0)); 296 | static const T diffLog = logScale(T(1)) - minLog; 297 | return normalizedLogScale(val, minLog, diffLog); 298 | } 299 | 300 | 301 | template 302 | inline const T& min(const T& a, const T& b, const T& c) 303 | { 304 | return std::min(std::min(a, b), c); 305 | } 306 | 307 | template 308 | inline const T& min(const T& a, const T& b, const T& c, const T& d) 309 | { 310 | return std::min(min(a, b, c), d); 311 | } 312 | 313 | template 314 | inline const T& min(const T& a, const T& b, const T& c, const T& d, const T& e) 315 | { 316 | return std::min(min(a, b, c, d), e); 317 | } 318 | 319 | template 320 | inline const T& max(const T& a, const T& b, const T& c) 321 | { 322 | return std::max(std::max(a, b), c); 323 | } 324 | 325 | template 326 | inline const T& max(const T& a, const T& b, const T& c, const T& d) 327 | { 328 | return std::max(max(a, b, c), d); 329 | } 330 | 331 | template 332 | inline const T& max(const T& a, const T& b, const T& c, const T& d, const T& e) 333 | { 334 | return std::max(max(a, b, c, d), e); 335 | } 336 | 337 | template 338 | inline T square(T value) 339 | { 340 | return value*value; 341 | } 342 | 343 | std::string getExtension(const std::string& filename); 344 | std::string getBasename(const std::string& filename); 345 | 346 | 347 | const std::vector & channelNames(); 348 | const std::vector & blendModeNames(); 349 | std::string channelToString(EChannel channel); 350 | std::string blendModeToString(EBlendMode mode); 351 | 352 | 353 | inline int codePointLength(char first) 354 | { 355 | if ((first & 0xf8) == 0xf0) 356 | return 4; 357 | else if ((first & 0xf0) == 0xe0) 358 | return 3; 359 | else if ((first & 0xe0) == 0xc0) 360 | return 2; 361 | else 362 | return 1; 363 | } 364 | 365 | std::vector split(std::string text, const std::string& delim); 366 | std::string toLower(std::string str); 367 | std::string toUpper(std::string str); 368 | bool matches(std::string text, std::string filter, bool isRegex); 369 | 370 | 371 | enum EDirection 372 | { 373 | Forward, 374 | Backward, 375 | }; -------------------------------------------------------------------------------- /src/GLImage.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 "GLImage.h" 8 | #include "Common.h" 9 | #include "Timer.h" 10 | #include "Colorspace.h" 11 | #include "ParallelFor.h" 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include "MultiGraph.h" 18 | 19 | using namespace nanogui; 20 | using namespace Eigen; 21 | using namespace std; 22 | 23 | shared_ptr ImageStatistics::computeStatistics(const HDRImage &img, float exposure) 24 | { 25 | static const int numBins = 256; 26 | static const int numTicks = 8; 27 | float displayMax = pow(2.f, -exposure); 28 | 29 | auto ret = make_shared(); 30 | for (int i = 0; i < ENumAxisScales; ++i) 31 | ret->histogram[i].values = MatrixX3f::Zero(numBins, 3); 32 | 33 | ret->exposure = exposure; 34 | ret->average = 0; 35 | 36 | ret->maximum = img.max().Color3::max(); 37 | ret->minimum = img.min().Color3::min(); 38 | 39 | Color4 gain(pow(2.f, exposure), 1.f); 40 | float d = 1.f / (img.width() * img.height()); 41 | 42 | for (Eigen::DenseIndex i = 0; i < img.size(); ++i) 43 | { 44 | Color4 val = gain * img(i); 45 | ret->average += val[0] + val[1] + val[2]; 46 | 47 | for (int c = 0; c < 3; ++c) 48 | { 49 | ret->histogram[ELinear].values(clamp(int(floor(val[c] * numBins)), 0, numBins - 1), c) += d; 50 | ret->histogram[ESRGB].values(clamp(int(floor(LinearToSRGB(val[c]) * numBins)), 0, numBins - 1), c) += d; 51 | ret->histogram[ELog].values(clamp(int(floor(normalizedLogScale(val[c]) * numBins)), 0, numBins - 1), c) += d; 52 | } 53 | } 54 | 55 | ret->average /= 3 * img.width() * img.height(); 56 | 57 | 58 | // Normalize each histogram according to its 10th-largest bin 59 | MatrixXf temp; 60 | for (int i = 0; i < ENumAxisScales; ++i) 61 | { 62 | temp = ret->histogram[i].values; 63 | DenseIndex idx = temp.size() - 10; 64 | nth_element(temp.data(), temp.data() + idx, temp.data() + temp.size()); 65 | ret->histogram[i].values /= temp(idx); 66 | } 67 | 68 | // create the tick marks 69 | ret->histogram[ELinear].xTicks.setLinSpaced(numTicks+1, 0.0f, 1.0f); 70 | ret->histogram[ESRGB].xTicks = ret->histogram[ELinear].xTicks.unaryExpr([](float v){return LinearToSRGB(v);}); 71 | ret->histogram[ELog].xTicks = ret->histogram[ELinear].xTicks.unaryExpr([](float v){return normalizedLogScale(v);}); 72 | 73 | // create the tick labels 74 | auto & hist = ret->histogram[ELinear]; 75 | hist.xTickLabels.resize(numTicks + 1); 76 | for (int i = 0; i <= numTicks; ++i) 77 | hist.xTickLabels[i] = fmt::format("{:.3f}", displayMax * hist.xTicks[i]); 78 | ret->histogram[ESRGB].xTickLabels = ret->histogram[ELog].xTickLabels = hist.xTickLabels; 79 | 80 | return ret; 81 | } 82 | 83 | 84 | 85 | LazyGLTextureLoader::~LazyGLTextureLoader() 86 | { 87 | if (m_texture) 88 | glDeleteTextures(1, &m_texture); 89 | } 90 | 91 | bool LazyGLTextureLoader::uploadToGPU(const std::shared_ptr &img, 92 | int milliseconds, 93 | int chunkSize) 94 | { 95 | if (img->isNull()) 96 | { 97 | m_dirty = false; 98 | return false; 99 | } 100 | 101 | // check if we need to upload the image to the GPU 102 | if (!m_dirty && m_texture) 103 | return false; 104 | 105 | Timer timer; 106 | // Allocate texture memory for the image 107 | if (!m_texture) 108 | glGenTextures(1, &m_texture); 109 | 110 | glBindTexture(GL_TEXTURE_2D, m_texture); 111 | 112 | // allocate a new texture and set parameters only if this is the first scanline 113 | if (m_nextScanline == 0) 114 | { 115 | glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 116 | img->width(), img->height(), 117 | 0, GL_RGBA, GL_FLOAT, nullptr); 118 | 119 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); 120 | // glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); 121 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); 122 | 123 | glPixelStorei(GL_UNPACK_ROW_LENGTH, img->width()); 124 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER); 125 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER); 126 | const GLfloat borderColor[] = { 0.0f, 0.0f, 0.0f, 0.0f }; 127 | glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor); 128 | } 129 | 130 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0); 131 | 132 | int maxLines = max(1, chunkSize / img->width()); 133 | while (true) 134 | { 135 | // compute tile size, accounting for partial tiles at boundary 136 | int remaining = img->height() - m_nextScanline; 137 | int numLines = std::min(maxLines, remaining); 138 | 139 | glPixelStorei(GL_UNPACK_SKIP_ROWS, m_nextScanline); 140 | glTexSubImage2D(GL_TEXTURE_2D, 141 | 0, // level 142 | 0, m_nextScanline, // xoffset, yoffset 143 | img->width(), numLines, // tile width and height 144 | GL_RGBA, // format 145 | GL_FLOAT, // type 146 | (const GLvoid *) img->data()); 147 | 148 | m_nextScanline += maxLines; 149 | 150 | if (m_nextScanline >= img->height()) 151 | { 152 | // done 153 | m_nextScanline = -1; 154 | m_dirty = false; 155 | break; 156 | } 157 | if (timer.elapsed() > milliseconds) 158 | break; 159 | } 160 | 161 | m_uploadTime += timer.lap(); 162 | 163 | if (!m_dirty) 164 | { 165 | spdlog::get("console")->trace("Uploading texture to GPU took {} ms", m_uploadTime); 166 | glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 1000); 167 | glGenerateMipmap(GL_TEXTURE_2D); //Generate num_mipmaps number of mipmaps here. 168 | spdlog::get("console")->trace("Generating mipmaps took {} ms", timer.lap()); 169 | } 170 | 171 | return !m_dirty; 172 | } 173 | 174 | 175 | 176 | GLImage::GLImage() : 177 | m_image(make_shared()), 178 | m_filename(), 179 | m_cachedHistogramExposure(NAN), 180 | m_histogramDirty(true), 181 | m_imageModifyDoneCallback(GLImage::VoidVoidFunc()) 182 | { 183 | // empty 184 | } 185 | 186 | 187 | GLImage::~GLImage() 188 | { 189 | 190 | } 191 | 192 | float GLImage::progress() const 193 | { 194 | checkAsyncResult(); 195 | return m_asyncCommand ? m_asyncCommand->progress() : 1.0f; 196 | } 197 | bool GLImage::isModified() const { checkAsyncResult(); return m_history.isModified(); } 198 | bool GLImage::hasUndo() const { checkAsyncResult(); return m_history.hasUndo(); } 199 | bool GLImage::hasRedo() const { checkAsyncResult(); return m_history.hasRedo(); } 200 | 201 | bool GLImage::canModify() const 202 | { 203 | return !m_asyncCommand; 204 | } 205 | 206 | void GLImage::asyncModify(const ImageCommandWithProgress & command) 207 | { 208 | // make sure any pending edits are done 209 | waitForAsyncResult(); 210 | 211 | m_asyncCommand = make_shared>([this,command](AtomicProgress & prog){return command(m_image, prog);}); 212 | m_asyncRetrieved = false; 213 | m_asyncCommand->compute(); 214 | } 215 | 216 | void GLImage::asyncModify(const ImageCommand &command) 217 | { 218 | // make sure any pending edits are done 219 | waitForAsyncResult(); 220 | 221 | m_asyncCommand = make_shared>([this,command](void){return command(m_image);}); 222 | m_asyncRetrieved = false; 223 | m_asyncCommand->compute(); 224 | } 225 | 226 | bool GLImage::undo() 227 | { 228 | // make sure any pending edits are done 229 | waitForAsyncResult(); 230 | 231 | if (m_history.undo(m_image)) 232 | { 233 | m_histogramDirty = true; 234 | m_texture.setDirty(); 235 | return true; 236 | } 237 | return false; 238 | } 239 | 240 | bool GLImage::redo() 241 | { 242 | // make sure any pending edits are done 243 | waitForAsyncResult(); 244 | 245 | if (m_history.redo(m_image)) 246 | { 247 | m_histogramDirty = true; 248 | m_texture.setDirty(); 249 | return true; 250 | } 251 | return false; 252 | } 253 | 254 | bool GLImage::checkAsyncResult() const 255 | { 256 | if (!m_asyncCommand || !m_asyncCommand->ready()) 257 | return false; 258 | 259 | return waitForAsyncResult(); 260 | } 261 | 262 | void GLImage::modifyFinished() const 263 | { 264 | m_asyncCommand = nullptr; 265 | if (m_imageModifyDoneCallback) 266 | m_imageModifyDoneCallback(); 267 | } 268 | 269 | 270 | bool GLImage::waitForAsyncResult() const 271 | { 272 | // nothing to wait for 273 | if (!m_asyncCommand) 274 | return false; 275 | 276 | if (!m_asyncRetrieved) 277 | { 278 | // now retrieve the result and copy it out of the async task 279 | auto result = m_asyncCommand->get(); 280 | 281 | // if there is no undo, treat this as an image load 282 | if (!result.second) 283 | { 284 | if (result.first) 285 | { 286 | m_history = CommandHistory(); 287 | m_image = result.first; 288 | } 289 | } 290 | else 291 | { 292 | m_history.addCommand(result.second); 293 | m_image = result.first; 294 | } 295 | 296 | m_asyncRetrieved = true; 297 | m_histogramDirty = true; 298 | m_texture.setDirty(); 299 | 300 | if (!result.first) 301 | { 302 | // image loading failed 303 | modifyFinished(); 304 | return false; 305 | } 306 | } 307 | 308 | // now set the progress bar to busy as we upload to GPU 309 | m_asyncCommand->setProgress(-1.f); 310 | 311 | uploadToGPU(); 312 | 313 | return true; 314 | } 315 | 316 | 317 | void GLImage::uploadToGPU() const 318 | { 319 | if (m_texture.uploadToGPU(m_image)) 320 | // now that we grabbed the results and uploaded to GPU, destroy the task 321 | modifyFinished(); 322 | } 323 | 324 | 325 | GLuint GLImage::glTextureId() const 326 | { 327 | checkAsyncResult(); 328 | uploadToGPU(); 329 | return m_texture.textureID(); 330 | } 331 | 332 | 333 | bool GLImage::load(const std::string & filename) 334 | { 335 | // make sure any pending edits are done 336 | waitForAsyncResult(); 337 | 338 | m_history = CommandHistory(); 339 | m_filename = filename; 340 | m_histogramDirty = true; 341 | m_texture.setDirty(); 342 | return m_image->load(filename); 343 | } 344 | 345 | bool GLImage::save(const std::string & filename, 346 | float gain, float gamma, 347 | bool sRGB, bool dither) const 348 | { 349 | // make sure any pending edits are done 350 | waitForAsyncResult(); 351 | 352 | if (!m_image->save(filename, gain, gamma, sRGB, dither)) 353 | return false; 354 | 355 | m_history.markSaved(); 356 | // setFilename(filename); 357 | 358 | return true; 359 | } 360 | 361 | void GLImage::recomputeHistograms(float exposure) const 362 | { 363 | checkAsyncResult(); 364 | 365 | if ((!m_histograms || m_histogramDirty || exposure != m_cachedHistogramExposure) && !m_image->isNull()) 366 | { 367 | m_histograms = make_shared( 368 | [this,exposure](void) 369 | { 370 | return ImageStatistics::computeStatistics(*m_image, exposure); 371 | }); 372 | m_histograms->compute(); 373 | m_histogramDirty = false; 374 | m_cachedHistogramExposure = exposure; 375 | } 376 | } -------------------------------------------------------------------------------- /src/HDRImage.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 // for Array, CwiseUnaryOp, Dynamic, DenseC... 10 | #include // for function 11 | #include // for vector 12 | #include // for string 13 | #include "Color.h" // for Color4, max, min 14 | #include "Progress.h" 15 | 16 | 17 | //! Floating point image 18 | class HDRImage : public Eigen::Array 19 | { 20 | using Base = Eigen::Array; 21 | public: 22 | using Intensity = Eigen::Array; 23 | 24 | public: 25 | //----------------------------------------------------------------------- 26 | //@{ \name Constructors, destructors, etc. 27 | //----------------------------------------------------------------------- 28 | HDRImage(void) : Base() {} 29 | HDRImage(int w, int h) : Base(w, h) {} 30 | HDRImage(const Base & other) : Base(other) {} 31 | 32 | //! This constructor allows you to construct a HDRImage from Eigen expressions 33 | template 34 | HDRImage(const Eigen::ArrayBase& other) : Base(other) { } 35 | 36 | //! This method allows you to assign Eigen expressions to a HDRImage 37 | template 38 | HDRImage& operator=(const Eigen::ArrayBase & other) 39 | { 40 | this->Base::operator=(other); 41 | return *this; 42 | } 43 | //@} 44 | 45 | int width() const { return (int)rows(); } 46 | int height() const { return (int)cols(); } 47 | bool isNull() const { return rows() == 0 || cols() == 0; } 48 | 49 | const Intensity & intensity() const { return m_intensity; } 50 | Intensity & intensity() { return m_intensity; } 51 | 52 | void setAlpha(float a) 53 | { 54 | *this = unaryExpr([a](const Color4 & c){return Color4(c.r,c.g,c.b,a);}); 55 | } 56 | 57 | void setChannelFrom(int c, const HDRImage & other) 58 | { 59 | *this = binaryExpr(other, [c](const Color4 & a, const Color4 & b){Color4 ret = a; ret[c] = b[c]; return ret;}); 60 | } 61 | 62 | Color4 min() const 63 | { 64 | Color4 m = (*this)(0,0); 65 | for (int y = 0; y < height(); ++y) 66 | for (int x = 0; x < width(); ++x) 67 | m = ::min(m, (*this)(x,y)); 68 | return m; 69 | } 70 | 71 | Color4 max() const 72 | { 73 | Color4 m = (*this)(0,0); 74 | for (int y = 0; y < height(); ++y) 75 | for (int x = 0; x < width(); ++x) 76 | m = ::max(m, (*this)(x,y)); 77 | return m; 78 | } 79 | 80 | //----------------------------------------------------------------------- 81 | //@{ \name Pixel accessors. 82 | //----------------------------------------------------------------------- 83 | enum BorderMode : int 84 | { 85 | BLACK = 0, 86 | EDGE, 87 | REPEAT, 88 | MIRROR 89 | }; 90 | static const std::vector & borderModeNames(); 91 | Color4 & pixel(int x, int y, BorderMode mX = EDGE, BorderMode mY = EDGE); 92 | const Color4 & pixel(int x, int y, BorderMode mX = EDGE, BorderMode mY = EDGE) const; 93 | //@} 94 | 95 | //----------------------------------------------------------------------- 96 | //@{ \name Pixel samplers. 97 | //----------------------------------------------------------------------- 98 | enum Sampler : int 99 | { 100 | NEAREST = 0, 101 | BILINEAR, 102 | BICUBIC 103 | }; 104 | static const std::vector & samplerNames(); 105 | Color4 sample(float sx, float sy, Sampler s, BorderMode mX = EDGE, BorderMode mY = EDGE) const; 106 | Color4 bilinear(float sx, float sy, BorderMode mX = EDGE, BorderMode mY = EDGE) const; 107 | Color4 bicubic(float sx, float sy, BorderMode mX = EDGE, BorderMode mY = EDGE) const; 108 | Color4 nearest(float sx, float sy, BorderMode mX = EDGE, BorderMode mY = EDGE) const; 109 | //@} 110 | 111 | 112 | //----------------------------------------------------------------------- 113 | //@{ \name Resizing. 114 | //----------------------------------------------------------------------- 115 | enum CanvasAnchor : int 116 | { 117 | TOP_LEFT = 0, 118 | TOP_CENTER, 119 | TOP_RIGHT, 120 | MIDDLE_LEFT, 121 | MIDDLE_CENTER, 122 | MIDDLE_RIGHT, 123 | BOTTOM_LEFT, 124 | BOTTOM_CENTER, 125 | BOTTOM_RIGHT, 126 | NUM_CANVAS_ANCHORS 127 | }; 128 | HDRImage resizedCanvas(int width, int height, CanvasAnchor anchor, const Color4 & bgColor) const; 129 | HDRImage resized(int width, int height) const; 130 | HDRImage resampled(int width, int height, 131 | AtomicProgress progress = AtomicProgress(), 132 | std::function warpFn = 133 | [](const Eigen::Vector2f &uv) { return uv; }, 134 | int superSample = 1, Sampler s = NEAREST, BorderMode mX = REPEAT, BorderMode mY = REPEAT) const; 135 | //@} 136 | 137 | 138 | //----------------------------------------------------------------------- 139 | //@{ \name Transformations. 140 | //----------------------------------------------------------------------- 141 | HDRImage flippedVertical() const {HDRImage img = rowwise().reverse().eval(); img.intensity() = intensity().rowwise().reverse().eval(); return img;} 142 | HDRImage flippedHorizontal() const {HDRImage img = colwise().reverse().eval(); img.intensity() = intensity().colwise().reverse().eval(); return img;} 143 | HDRImage rotated90CW() const {HDRImage img = transpose().colwise().reverse().eval(); img.intensity() = intensity().transpose().colwise().reverse().eval(); return img;} 144 | HDRImage rotated90CCW() const {HDRImage img = transpose().rowwise().reverse().eval(); img.intensity() = intensity().transpose().rowwise().reverse().eval(); return img;} 145 | //@} 146 | 147 | 148 | //----------------------------------------------------------------------- 149 | //@{ \name Bayer demosaicing. 150 | //----------------------------------------------------------------------- 151 | void bayerMosaic(const Eigen::Vector2i &redOffset); 152 | 153 | void demosaicLinear(const Eigen::Vector2i &redOffset) 154 | { 155 | demosaicGreenLinear(redOffset); 156 | demosaicRedBlueLinear(redOffset); 157 | } 158 | void demosaicGreenGuidedLinear(const Eigen::Vector2i &redOffset) 159 | { 160 | demosaicGreenLinear(redOffset); 161 | demosaicRedBlueGreenGuidedLinear(redOffset); 162 | } 163 | void demosaicMalvar(const Eigen::Vector2i &redOffset) 164 | { 165 | demosaicGreenMalvar(redOffset); 166 | demosaicRedBlueMalvar(redOffset); 167 | } 168 | void demosaicAHD(const Eigen::Vector2i &redOffset, const Eigen::Matrix3f &cameraToXYZ); 169 | 170 | // green channel 171 | void demosaicGreenLinear(const Eigen::Vector2i &redOffset); 172 | void demosaicGreenHorizontal(const HDRImage &raw, const Eigen::Vector2i &redOffset); 173 | void demosaicGreenVertical(const HDRImage &raw, const Eigen::Vector2i &redOffset); 174 | void demosaicGreenMalvar(const Eigen::Vector2i &redOffset); 175 | void demosaicGreenPhelippeau(const Eigen::Vector2i &redOffset); 176 | 177 | // red/blue channels 178 | void demosaicRedBlueLinear(const Eigen::Vector2i &redOffset); 179 | void demosaicRedBlueGreenGuidedLinear(const Eigen::Vector2i &redOffset); 180 | void demosaicRedBlueMalvar(const Eigen::Vector2i &redOffset); 181 | 182 | void demosaicBorder(size_t border); 183 | 184 | HDRImage medianFilterBayerArtifacts() const; 185 | //@} 186 | 187 | 188 | //----------------------------------------------------------------------- 189 | //@{ \name Image filters. 190 | //----------------------------------------------------------------------- 191 | HDRImage inverted() const; 192 | HDRImage brightnessContrast(float brightness, float contrast, bool linear, EChannel c) const; 193 | HDRImage convolved(const Eigen::ArrayXXf &kernel, 194 | AtomicProgress progress, 195 | BorderMode mX = EDGE, BorderMode mY = EDGE) const; 196 | HDRImage GaussianBlurred(float sigmaX, float sigmaY, 197 | AtomicProgress progress, 198 | BorderMode mX = EDGE, BorderMode mY = EDGE, 199 | float truncateX = 6.0f, float truncateY = 6.0f) const; 200 | HDRImage GaussianBlurredX(float sigmaX, 201 | AtomicProgress progress, 202 | BorderMode mode = EDGE, float truncateX = 6.0f) const; 203 | HDRImage GaussianBlurredY(float sigmaY, 204 | AtomicProgress progress, 205 | BorderMode mode = EDGE, float truncateY = 6.0f) const; 206 | HDRImage iteratedBoxBlurred(float sigma, int iterations = 6, AtomicProgress progress = AtomicProgress(), BorderMode mX = EDGE, BorderMode mY = EDGE) const; 207 | HDRImage fastGaussianBlurred(float sigmaX, float sigmaY, 208 | AtomicProgress progress, 209 | BorderMode mX = EDGE, BorderMode mY = EDGE) const; 210 | HDRImage boxBlurred(int w, AtomicProgress progress, 211 | BorderMode mX = EDGE, BorderMode mY = EDGE) const 212 | { 213 | return boxBlurred(w, w, progress, mX, mY); 214 | } 215 | HDRImage boxBlurred(int hw, int hh, AtomicProgress progress, 216 | BorderMode mX = EDGE, BorderMode mY = EDGE) const 217 | { 218 | return boxBlurredX(hw, AtomicProgress(progress, 0.5f), mX).boxBlurredY(hh, AtomicProgress(progress, 0.5f), mY); 219 | } 220 | HDRImage boxBlurredX(int leftSize, int rightSize, AtomicProgress progress, BorderMode mode = EDGE) const; 221 | HDRImage boxBlurredX(int halfSize, AtomicProgress progress, 222 | BorderMode mode = EDGE) const {return boxBlurredX(halfSize, halfSize, progress, mode);} 223 | HDRImage boxBlurredY(int upSize, int downSize, AtomicProgress progress, BorderMode mode = EDGE) const; 224 | HDRImage boxBlurredY(int halfSize, AtomicProgress progress, 225 | BorderMode mode = EDGE) const {return boxBlurredY(halfSize, halfSize, progress, mode);} 226 | HDRImage unsharpMasked(float sigma, float strength, AtomicProgress progress, BorderMode mX = EDGE, BorderMode mY = EDGE) const; 227 | HDRImage medianFiltered(float radius, int channel, AtomicProgress progress, BorderMode mX = EDGE, BorderMode mY = EDGE, bool round = false) const; 228 | HDRImage medianFiltered(float r, AtomicProgress progress, BorderMode mX = EDGE, BorderMode mY = EDGE, bool round = false) const 229 | { 230 | return medianFiltered(r, 0, AtomicProgress(progress, 0.25f), mX, mY, round) 231 | .medianFiltered(r, 1, AtomicProgress(progress, 0.25f), mX, mY, round) 232 | .medianFiltered(r, 2, AtomicProgress(progress, 0.25f), mX, mY, round) 233 | .medianFiltered(r, 3, AtomicProgress(progress, 0.25f), mX, mY, round); 234 | } 235 | HDRImage bilateralFiltered(float sigmaRange/* = 0.1f*/, 236 | float sigmaDomain/* = 1.0f*/, 237 | AtomicProgress progress, 238 | BorderMode mX = EDGE, BorderMode mY = EDGE, 239 | float truncateDomain = 6.0f) const; 240 | //@} 241 | 242 | bool load(const std::string & filename); 243 | /*! 244 | * @brief Write the file to disk. 245 | * 246 | * The output image format is deduced from the filename extension. 247 | * 248 | * @param filename Filename to save to on disk 249 | * @param gain Multiply all pixel values by gain before saving 250 | * @param sRGB If not saving to an HDR format, tonemap the image to sRGB 251 | * @param gamma If not saving to an HDR format, tonemap the image using this gamma value 252 | * @param dither If not saving to an HDR format, dither when tonemapping down to 8-bit 253 | * @return True if writing was successful 254 | */ 255 | bool save(const std::string & filename, 256 | float gain, float gamma, 257 | bool sRGB, bool dither) const; 258 | 259 | public: 260 | EIGEN_MAKE_ALIGNED_OPERATOR_NEW 261 | 262 | private: 263 | Intensity m_intensity; 264 | }; 265 | 266 | 267 | std::shared_ptr loadImage(const std::string & filename); 268 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 3.1) 2 | project(hdrview) 3 | 4 | set(HDRVIEW_VERSION "0.2") 5 | 6 | # Set ourselves as the startup project in visual studio. 7 | # Not available until cmake 3.6, but doesn't break older versions. 8 | set_property(DIRECTORY PROPERTY VS_STARTUP_PROJECT hdrview) 9 | 10 | #============================================================================ 11 | # Set a default build configuration (Release) 12 | #============================================================================ 13 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 14 | message(STATUS "Setting build type to 'Release' as none was specified.") 15 | set(CMAKE_BUILD_TYPE Release CACHE STRING "Choose the type of build." FORCE) 16 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" 17 | "MinSizeRel" "RelWithDebInfo") 18 | endif() 19 | string(TOUPPER "${CMAKE_BUILD_TYPE}" U_CMAKE_BUILD_TYPE) 20 | 21 | #============================================================================ 22 | # Enable folders for projects in Visual Studio 23 | #============================================================================ 24 | if (CMAKE_GENERATOR MATCHES "Visual Studio") 25 | set_property(GLOBAL PROPERTY USE_FOLDERS ON) 26 | endif() 27 | 28 | if (APPLE) 29 | set(CMAKE_MACOSX_RPATH ON) 30 | endif() 31 | 32 | #============================================================================ 33 | # Set C++ standard 34 | #============================================================================ 35 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 36 | set(CMAKE_CXX_EXTENSIONS OFF) 37 | if(NOT CMAKE_CXX_STANDARD OR CMAKE_CXX_STANDARD LESS 11) 38 | set(CMAKE_CXX_STANDARD 11) 39 | endif() 40 | 41 | #============================================================================ 42 | # Enable link time optimization and set the default symbol 43 | # visibility to hidden (very important to obtain small binaries) 44 | #============================================================================ 45 | if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") 46 | if (NOT ${U_CMAKE_BUILD_TYPE} MATCHES DEBUG) 47 | # Default symbol visibility 48 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden") 49 | endif() 50 | endif() 51 | 52 | #============================================================================ 53 | # Sanitize build environment for static build with C++11 54 | #============================================================================ 55 | if (MSVC) 56 | # Disable annoying secure CRT warnings 57 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /D_CRT_SECURE_NO_WARNINGS") 58 | 59 | # Parallel build on MSVC (all targets) 60 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") 61 | 62 | if (NOT CMAKE_SIZEOF_VOID_P EQUAL 8) 63 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /arch:SSE2") 64 | 65 | # Disable Eigen vectorization for Windows 32 bit builds (issues with unaligned access segfaults) 66 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /DEIGEN_DONT_ALIGN") 67 | endif() 68 | 69 | # Static build 70 | set(CompilerFlags 71 | CMAKE_CXX_FLAGS CMAKE_CXX_FLAGS_DEBUG CMAKE_CXX_FLAGS_RELEASE 72 | CMAKE_CXX_FLAGS_MINSIZEREL CMAKE_CXX_FLAGS_RELWITHDEBINFO 73 | CMAKE_C_FLAGS CMAKE_C_FLAGS_DEBUG CMAKE_C_FLAGS_RELEASE 74 | CMAKE_C_FLAGS_MINSIZEREL CMAKE_C_FLAGS_RELWITHDEBINFO) 75 | foreach(CompilerFlag ${CompilerFlags}) 76 | string(REPLACE "/MD" "/MT" ${CompilerFlag} "${${CompilerFlag}}") 77 | endforeach() 78 | elseif(APPLE) 79 | # silence GL deprecation warnings. 80 | add_definitions(-DGL_SILENCE_DEPRECATION) 81 | # Try to auto-detect a suitable SDK 82 | # Commented out for now -- causes a too new SDK to be selected on AppVeyor 83 | #execute_process(COMMAND bash -c "xcodebuild -version -sdk | grep MacOSX | grep Path | head -n 1 | cut -f 2 -d ' '" OUTPUT_VARIABLE CMAKE_OSX_SYSROOT) 84 | #string(REGEX REPLACE "(\r?\n)+$" "" CMAKE_OSX_SYSROOT "${CMAKE_OSX_SYSROOT}") 85 | #string(REGEX REPLACE "^.*X([0-9.]*).sdk$" "\\1" CMAKE_OSX_DEPLOYMENT_TARGET "${CMAKE_OSX_SYSROOT}") 86 | endif() 87 | 88 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/ext) 89 | 90 | #============================================================================ 91 | # Compile remainder of the codebase with compiler warnings turned on 92 | #============================================================================ 93 | if(MSVC) 94 | if(CMAKE_CXX_FLAGS MATCHES "/W[0-4]") 95 | string(REGEX REPLACE "/W[0-4]" "/W4" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") 96 | else() 97 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") 98 | endif() 99 | elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") 100 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wno-unused-parameter") 101 | if (CMAKE_CXX_COMPILER_ID MATCHES "Clang") 102 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-gnu-anonymous-struct -Wno-c99-extensions -Wno-nested-anon-types -Wno-deprecated-register") 103 | endif() 104 | endif() 105 | 106 | include_directories( 107 | # GLFW library for OpenGL context creation 108 | ${GLFW_INCLUDE_DIR} 109 | # GLEW library for accessing OpenGL functions 110 | ${GLEW_INCLUDE_DIR} 111 | # stb 112 | ${STB_INCLUDE_DIR} 113 | # tinydngloader 114 | ${TINYDNG_INCLUDE_DIR} 115 | # NanoGUI user interface library 116 | ${NANOGUI_INCLUDE_DIR} 117 | ${NANOGUI_EXTRA_INCS} 118 | # OpenEXR high dynamic range bitmap library 119 | ${OPENEXR_INCLUDE_DIRS} 120 | # tinydir 121 | ${TINYDIR_INCLUDE_DIR} 122 | # tinynpy 123 | ${TINYNPY_INCLUDE_DIR} 124 | # docopt 125 | ${DOCOPT_INCLUDE_DIR} 126 | # spdlog 127 | ${SPDLOG_INCLUDE_DIR} 128 | # boost REGEX 129 | ${Boost_INCLUDE_DIRS} 130 | ) 131 | 132 | # Resource file (icons etc.) 133 | set(EXTRA_SOURCE "") 134 | if (APPLE) 135 | set(EXTRA_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/resources/icon.icns") 136 | elseif(WIN32) 137 | set(EXTRA_SOURCE "${CMAKE_CURRENT_SOURCE_DIR}/resources/icon.rc") 138 | endif() 139 | 140 | add_executable(HDRView 141 | src/Async.h 142 | src/Color.cpp 143 | src/Color.h 144 | src/Colorspace.cpp 145 | src/Colorspace.h 146 | src/CommandHistory.h 147 | src/Common.cpp 148 | src/Common.h 149 | src/DitherMatrix256.h 150 | src/EditImagePanel.cpp 151 | src/EditImagePanel.h 152 | src/EnvMap.cpp 153 | src/EnvMap.h 154 | src/FilmicToneCurve.cpp 155 | src/FilmicToneCurve.h 156 | src/Fwd.h 157 | src/GLImage.cpp 158 | src/GLImage.h 159 | src/HDRImage.cpp 160 | src/HDRImage.h 161 | src/HDRImageIO.cpp 162 | src/HDRImageViewer.cpp 163 | src/HDRImageViewer.h 164 | src/HDRView.cpp 165 | src/HDRViewer.cpp 166 | src/HDRViewer.h 167 | src/HelpWindow.cpp 168 | src/HelpWindow.h 169 | src/HSLGradient.cpp 170 | src/HSLGradient.h 171 | src/ImageButton.cpp 172 | src/ImageButton.h 173 | src/ImageListPanel.cpp 174 | src/ImageListPanel.h 175 | src/ImageShader.cpp 176 | src/ImageShader.h 177 | src/MultiGraph.cpp 178 | src/MultiGraph.h 179 | src/ParallelFor.cpp 180 | src/ParallelFor.h 181 | src/PFM.h 182 | src/PFM.cpp 183 | src/PPM.h 184 | src/PPM.cpp 185 | src/Progress.cpp 186 | src/Progress.h 187 | src/Range.h 188 | src/Timer.h 189 | src/Well.cpp 190 | src/Well.h 191 | ${EXTRA_SOURCE}) 192 | 193 | set(HDRVIEW_DEFINITIONS -DHDRVIEW_VERSION="${HDRVIEW_VERSION}") 194 | if (APPLE) 195 | # HDRVIEW is unlikely to switch away from openGL anytime soon 196 | set(HDRVIEW_DEFINITIONS ${HDRVIEW_DEFINITIONS} -DGL_SILENCE_DEPRECATION) 197 | endif() 198 | 199 | add_definitions(${HDRVIEW_DEFINITIONS} ${NANOGUI_EXTRA_DEFS}) 200 | 201 | set_target_properties(HDRView PROPERTIES OUTPUT_NAME "HDRView") 202 | 203 | if (APPLE) 204 | # Build an application bundle on OSX 205 | set_target_properties(HDRView PROPERTIES MACOSX_BUNDLE TRUE) 206 | set_target_properties(HDRView PROPERTIES MACOSX_BUNDLE_BUNDLE_NAME "HDRView") 207 | set_target_properties(HDRView PROPERTIES MACOSX_BUNDLE_INFO_STRING "High dynamic range image viewer and comparison tool, version ${HDRVIEW_VERSION}.") 208 | STRING(TIMESTAMP YEAR "%Y") 209 | set_target_properties(HDRView PROPERTIES MACOSX_BUNDLE_COPYRIGHT "Copyright ${YEAR}, Wojciech Jarosz. Freely available under the BSD license.") 210 | set_target_properties(HDRView PROPERTIES MACOSX_BUNDLE_BUNDLE_GUI_IDENTIFIER "com.im.HDRView") 211 | set_target_properties(HDRView PROPERTIES MACOSX_BUNDLE_ICON_FILE icon.icns) 212 | set_target_properties(HDRView PROPERTIES MACOSX_BUNDLE_BUNDLE_VERSION ${HDRVIEW_VERSION}) 213 | set_target_properties(HDRView PROPERTIES MACOSX_BUNDLE_SHORT_VERSION_STRING ${HDRVIEW_VERSION}) 214 | set_target_properties(HDRView PROPERTIES MACOSX_BUNDLE_LONG_VERSION_STRING ${HDRVIEW_VERSION}) 215 | set_target_properties(HDRView PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CMAKE_CURRENT_SOURCE_DIR}/resources/MacOSXBundleInfo.plist.in) 216 | set_target_properties(HDRView PROPERTIES RESOURCE resources/icon.icns) 217 | set_source_files_properties(resources/icon.icns PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") 218 | elseif (NOT WIN32) 219 | # Insulate from a few types of ABI changes by statically linking against libgcc and libstdc++ 220 | set_target_properties(HDRView PROPERTIES LINK_FLAGS "-static-libgcc") 221 | endif() 222 | 223 | if (UNIX AND NOT ${U_CMAKE_BUILD_TYPE} MATCHES DEBUG) 224 | add_custom_command(TARGET HDRView POST_BUILD COMMAND strip $) 225 | endif() 226 | 227 | add_executable(hdrbatch 228 | src/Color.cpp 229 | src/Color.h 230 | src/Colorspace.cpp 231 | src/Colorspace.h 232 | src/Common.cpp 233 | src/Common.h 234 | src/EnvMap.cpp 235 | src/EnvMap.h 236 | src/DitherMatrix256.h 237 | src/HDRImage.cpp 238 | src/HDRImage.h 239 | src/HDRImageIO.cpp 240 | src/HDRBatch.cpp 241 | src/ParallelFor.cpp 242 | src/ParallelFor.h 243 | src/PFM.cpp 244 | src/PFM.h 245 | src/PPM.cpp 246 | src/PPM.h 247 | src/Progress.cpp 248 | src/Progress.h 249 | src/Range.h) 250 | 251 | add_executable(force-random-dither 252 | src/forced-random-dither.cpp) 253 | 254 | target_link_libraries(HDRView IlmImf nanogui docopt_s TinyNPY ${NANOGUI_EXTRA_LIBS} ${Boost_REGEX_LIBRARY}) 255 | target_link_libraries(hdrbatch IlmImf docopt_s TinyNPY ${Boost_REGEX_LIBRARY}) 256 | target_link_libraries(force-random-dither nanogui ${NANOGUI_EXTRA_LIBS}) 257 | 258 | if (NOT ${CMAKE_VERSION} VERSION_LESS 3.3 AND IWYU) 259 | find_program(iwyu_path NAMES include-what-you-use iwyu) 260 | if (iwyu_path) 261 | set_property(TARGET HDRView hdrbatch force-random-dither PROPERTY CXX_INCLUDE_WHAT_YOU_USE ${iwyu_path}) 262 | endif() 263 | endif() 264 | 265 | if (APPLE) 266 | install(TARGETS HDRView BUNDLE DESTINATION "/Applications") 267 | install(SCRIPT resources/osx-post-install.cmake) 268 | elseif(WIN32) 269 | install(TARGETS HDRView RUNTIME DESTINATION "bin") 270 | else() 271 | install(TARGETS HDRView RUNTIME DESTINATION "bin") 272 | install(FILES resources/hdrview.desktop DESTINATION "/usr/share/applications") 273 | install(FILES resources/icon-512.png DESTINATION "/usr/share/icons/hicolor/1024x1024/apps" RENAME hdrview.png) 274 | install(FILES resources/icon-512.png DESTINATION "/usr/share/icons/hicolor/512x512/apps" RENAME hdrview.png) 275 | install(FILES resources/icon-256.png DESTINATION "/usr/share/icons/hicolor/256x256/apps" RENAME hdrview.png) 276 | install(FILES resources/icon-128.png DESTINATION "/usr/share/icons/hicolor/128x128/apps" RENAME hdrview.png) 277 | install(FILES resources/icon-96.png DESTINATION "/usr/share/icons/hicolor/96x96/apps" RENAME hdrview.png) 278 | install(FILES resources/icon-64.png DESTINATION "/usr/share/icons/hicolor/64x64/apps" RENAME hdrview.png) 279 | install(FILES resources/icon-48.png DESTINATION "/usr/share/icons/hicolor/48x48/apps" RENAME hdrview.png) 280 | install(FILES resources/icon-32.png DESTINATION "/usr/share/icons/hicolor/32x32/apps" RENAME hdrview.png) 281 | install(FILES resources/icon-24.png DESTINATION "/usr/share/icons/hicolor/24x24/apps" RENAME hdrview.png) 282 | install(FILES resources/icon-16.png DESTINATION "/usr/share/icons/hicolor/16x16/apps" RENAME hdrview.png) 283 | endif() 284 | --------------------------------------------------------------------------------