├── .gitignore ├── CMakeLists.txt ├── FoveatedBenchmarks.ipynb ├── README.md ├── inc ├── CL │ ├── cl.hpp │ └── cl2.hpp └── TOBIIRESEARCH │ ├── tobii_research.h │ ├── tobii_research_calibration.h │ ├── tobii_research_eyetracker.h │ └── tobii_research_streams.h ├── lbg-stippling ├── .clang-format ├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── dlls │ ├── Qt5Core.dll │ ├── Qt5Cored.dll │ ├── Qt5Gui.dll │ ├── Qt5Guid.dll │ ├── Qt5PrintSupport.dll │ ├── Qt5PrintSupportd.dll │ ├── Qt5Svg.dll │ ├── Qt5Svgd.dll │ ├── Qt5Widgets.dll │ └── Qt5Widgetsd.dll ├── input │ └── input1.jpg ├── main.cpp ├── resources.qrc └── src │ ├── lbgstippling.cpp │ ├── lbgstippling.h │ ├── mainwindow.cpp │ ├── mainwindow.h │ ├── nanoflann.hpp │ ├── settingswidget.cpp │ ├── settingswidget.h │ ├── shader │ ├── Voronoi.frag.h │ └── Voronoi.vert.h │ ├── stippleviewer.cpp │ ├── stippleviewer.h │ ├── voronoicell.cpp │ ├── voronoicell.h │ ├── voronoidiagram.cpp │ └── voronoidiagram.h ├── lib ├── tobii_research.dll └── tobii_research.lib ├── resources ├── eyeTracker.jpg ├── jet.png ├── mc1024_92702.zip ├── richtmyer.png ├── vortex.png └── zeiss.png ├── screenshots ├── 2017-12-19-cham.png ├── 2017-12-19-nova.png ├── 2018-04-23-cham.png └── 2018-04-23-nova.png └── src ├── core ├── volumerendercl.cpp └── volumerendercl.h ├── io ├── datrawreader.cpp └── datrawreader.h ├── kernel └── volumeraycast.cl ├── oclutil ├── openclglutilities.cpp ├── openclglutilities.h ├── openclutilities.cpp └── openclutilities.h └── qt ├── colorutils.cpp ├── colorutils.h ├── colorwheel.cpp ├── colorwheel.h ├── hoverpoints.cpp ├── hoverpoints.h ├── main.cpp ├── mainwindow.cpp ├── mainwindow.h ├── mainwindow.ui ├── transferfunctionwidget.cpp ├── transferfunctionwidget.h ├── volumerenderwidget.cpp └── volumerenderwidget.h /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | python_tests/ 3 | build-*/ 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ##################################### 2 | ##### OpenCL Volume Raycaster ##### 3 | ##### (c) 2018 Valentin Bruder ##### 4 | ##################################### 5 | 6 | cmake_minimum_required(VERSION 3.9) 7 | 8 | set(PROJECT "VolumeRaycasterCL") 9 | project(${PROJECT} VERSION 1.0 DESCRIPTION "Volume raycaster based on OpenCL" LANGUAGES CXX) 10 | 11 | # require c++14 12 | set(CMAKE_CXX_STANDARD 14) 13 | add_definitions(-DNOMINMAX) 14 | 15 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "bin") 16 | ## TODO: set install target 17 | # if (NOT DEFINED VolumeRaycasterCL_INSTALL_BIN_DIR) 18 | # set(VolumeRaycasterCL_INSTALL_BIN_DIR "bin") 19 | # endif() 20 | 21 | ### OpenCL 22 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}) 23 | find_package(OpenCL REQUIRED) 24 | include_directories(${OPENCL_INCLUDE_DIR}) 25 | 26 | find_package(OpenGL REQUIRED) 27 | find_package(OpenMP) 28 | 29 | ### Qt 30 | # Find includes in corresponding build directories 31 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 32 | # Instruct CMake to run moc automatically when needed 33 | set(CMAKE_AUTOMOC ON) 34 | # Create code from a list of Qt designer ui files 35 | set(CMAKE_AUTOUIC ON) 36 | # msvc only; mingw needs different logic 37 | IF(MSVC) 38 | SET(QT_MISSING True) 39 | # look for user-registry pointing to qtcreator 40 | GET_FILENAME_COMPONENT(QT_BIN [HKEY_CURRENT_USER\\Software\\Classes\\Applications\\QtProject.QtCreator.cpp\\shell\\Open\\Command] PATH) 41 | 42 | # get root path so we can search for 5.3, 5.4, 5.5, etc 43 | STRING(REPLACE "/Tools" ";" QT_BIN "${QT_BIN}") 44 | LIST(GET QT_BIN 0 QT_BIN) 45 | #FILE(GLOB QT_VERSIONS "${QT_BIN}/5.*") 46 | #LIST(SORT QT_VERSIONS) 47 | 48 | # assume the latest version will be last alphabetically 49 | #LIST(REVERSE QT_VERSIONS) 50 | #LIST(GET QT_VERSIONS 0 QT_VERSION) 51 | 52 | # fix any double slashes which seem to be common 53 | #STRING(REPLACE "//" "/" QT_VERSION "${QT_VERSION}") 54 | 55 | SET(QT_VERSION "C:/Qt/5.11.2") 56 | 57 | # infer the folder from Cmake msvc_version string 58 | # - qt uses (e.g.) "msvc2013" 59 | # - cmake uses (e.g.) "1800" 60 | # - see also https://cmake.org/cmake/help/v3.0/variable/MSVC_VERSION.html 61 | if(${MSVC_VERSION} VERSION_EQUAL "1800") 62 | SET(QT_MSVC 2013) 63 | elseif(${MSVC_VERSION} VERSION_EQUAL "1900") 64 | SET(QT_MSVC 2015) 65 | elseif(${MSVC_VERSION} VERSION_GREATER "1900") 66 | SET(QT_MSVC 2017) 67 | endif() 68 | 69 | # check for 64-bit os 70 | # may need to be removed for older compilers as it wasn't always offered 71 | IF(CMAKE_SYSTEM_PROCESSOR MATCHES 64) 72 | SET(QT_MSVC "${QT_MSVC}_64") 73 | ENDIF() 74 | SET(QT_PATH "${QT_VERSION}/msvc${QT_MSVC}") 75 | SET(QT_MISSING False) 76 | 77 | IF(NOT QT_MISSING) 78 | MESSAGE("-- Qt found: ${QT_PATH}") 79 | SET(Qt5_DIR "${QT_PATH}/lib/cmake/Qt5/") 80 | SET(Qt5Test_DIR "${QT_PATH}/lib/cmake/Qt5Test") 81 | ENDIF() 82 | ENDIF() 83 | 84 | # Find Qt libraries 85 | FIND_PACKAGE(Qt5 COMPONENTS Core Gui Widgets Concurrent REQUIRED) 86 | 87 | # Check Qt minor version 88 | if (Qt5Core_FOUND) 89 | if (Qt5Core_VERSION VERSION_LESS 5.10.0) 90 | message(FATAL_ERROR "Minimum supported Qt5 version is 5.10.0!") 91 | endif() 92 | else() 93 | message(SEND_ERROR "The Qt5 library could not be found!") 94 | endif(Qt5Core_FOUND) 95 | 96 | IF(${Qt5Gui_OPENGL_IMPLEMENTATION} MATCHES "GLES") 97 | MESSAGE(STATUS "Building an OpenGL ES build (${Qt5Gui_OPENGL_IMPLEMENTATION})") 98 | SET(STEL_GLES_LIBS Qt5::Gui_EGL Qt5::Gui_GLESv2) 99 | ENDIF() 100 | 101 | # set headers 102 | set(raycast_headers 103 | src/io/datrawreader.h 104 | src/oclutil/openclutilities.h 105 | src/oclutil/openclglutilities.h 106 | src/qt/mainwindow.h 107 | src/qt/transferfunctionwidget.h 108 | src/qt/volumerenderwidget.h 109 | src/qt/colorutils.h 110 | src/qt/colorwheel.h 111 | src/qt/hoverpoints.h 112 | src/core/volumerendercl.h 113 | inc/CL/cl2.hpp 114 | ) 115 | 116 | # set sources 117 | set(raycast_sources 118 | src/io/datrawreader.cpp 119 | src/oclutil/openclutilities.cpp 120 | src/oclutil/openclglutilities.cpp 121 | src/qt/main.cpp 122 | src/qt/mainwindow.cpp 123 | src/qt/mainwindow.ui 124 | src/qt/transferfunctionwidget.cpp 125 | src/qt/volumerenderwidget.cpp 126 | src/qt/colorutils.cpp 127 | src/qt/colorwheel.cpp 128 | src/qt/hoverpoints.cpp 129 | src/core/volumerendercl.cpp 130 | ) 131 | 132 | add_executable(${PROJECT} ${raycast_sources} ${raycast_headers}) 133 | 134 | # link Qt libraries 135 | target_link_libraries(${PROJECT} PRIVATE Qt5::Core Qt5::Widgets Qt5::Gui Qt5::Concurrent) 136 | # link OpenCL/OpenGL 137 | target_link_libraries(${PROJECT} PRIVATE OpenCL::OpenCL) 138 | target_link_libraries(${PROJECT} PRIVATE OpenGL::GL) 139 | # optional: link OpenMP 140 | if(OPENMP_FOUND) 141 | target_link_libraries(${PROJECT} PRIVATE OpenMP::OpenMP_CXX) 142 | endif() 143 | 144 | # include tobii research sdk 145 | find_library(TOBII_LIBRARY NAME "tobii_research" PATHS "${CMAKE_CURRENT_SOURCE_DIR}/lib/") 146 | if(TOBII_LIBRARY) 147 | target_link_libraries(${PROJECT} PRIVATE ${TOBII_LIBRARY}) 148 | configure_file("${CMAKE_CURRENT_SOURCE_DIR}/lib/tobii_research.dll" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Release/tobii_research.dll" COPYONLY) 149 | configure_file("${CMAKE_CURRENT_SOURCE_DIR}/lib/tobii_research.dll" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Debug/tobii_research.dll" COPYONLY) 150 | endif() 151 | 152 | # copy runtime files 153 | IF(MSVC) 154 | # copy OpenCL kernel source file to build directory to support start from within VS (compiled @ runtime) 155 | configure_file(./src/kernel/volumeraycast.cl ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/../kernels/volumeraycast.cl COPYONLY) 156 | ## copy Qt dlls and kernel in runtime directory 157 | # release 158 | configure_file("${QT_PATH}/bin/Qt5Core.dll" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Release/Qt5Core.dll" COPYONLY) 159 | configure_file("${QT_PATH}/bin/Qt5Gui.dll" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Release/Qt5Gui.dll" COPYONLY) 160 | configure_file("${QT_PATH}/bin/Qt5Widgets.dll" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Release/Qt5Widgets.dll" COPYONLY) 161 | configure_file("${QT_PATH}/bin/Qt5Concurrent.dll" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Release/Qt5Concurrent.dll" COPYONLY) 162 | configure_file(./src/kernel/volumeraycast.cl ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Debug/kernels/volumeraycast.cl COPYONLY) 163 | # debug 164 | configure_file("${QT_PATH}/bin/Qt5Cored.dll" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Debug/Qt5Cored.dll" COPYONLY) 165 | configure_file("${QT_PATH}/bin/Qt5Guid.dll" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Debug/Qt5Guid.dll" COPYONLY) 166 | configure_file("${QT_PATH}/bin/Qt5Widgetsd.dll" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Debug/Qt5Widgetsd.dll" COPYONLY) 167 | configure_file("${QT_PATH}/bin/Qt5Concurrentd.dll" "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Debug/Qt5Concurrentd.dll" COPYONLY) 168 | configure_file(./src/kernel/volumeraycast.cl ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/Release/kernels/volumeraycast.cl COPYONLY) 169 | ELSE() 170 | # copy OpenCL kernel source file to build directory (compiled @ runtime) 171 | configure_file(./src/kernel/volumeraycast.cl ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/kernels/volumeraycast.cl COPYONLY) 172 | ENDIF() 173 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Description # 2 | 3 | An Voronoi-based foveated volume renderer. Check out our EuroVis 2019 short paper for more details: https://vbruder.github.io/publication/bruder-sbfwe-19/bruder-sbfwe-19.pdf 4 | 5 | The volume renderer is based on my OpenCL volume renderer **VolumeRendererCL**. 6 | Check out the [repo](https://github.com/vbruder/VolumeRendererCL) for more details on the code, supported data sets and how to build. 7 | 8 | The Weighted-Linde-Buzo-Gray algorithm can be used to generate the sampling mask in a preprocessing step. 9 | Use the `lbg-stippling` to generate a custom sampling mask or use the pre-created one in `resources/mc1024_92702.zip` (fixed to an output size of 1024x1024px). 10 | 11 | For Tobii eye tracking support, you also need to link the respective library. 12 | 13 | ## Screenshots ## 14 | 15 | ![eye-tracker](https://github.com/vbruder/FoveatedVolumeRendering/blob/master/resources/eyeTracker.jpg) 16 | ![richtmyer](https://github.com/vbruder/FoveatedVolumeRendering/blob/master/resources/richtmyer.png) 17 | ![vortex](https://github.com/vbruder/FoveatedVolumeRendering/blob/master/resources/vortex.png) 18 | 19 | *Copyright belongs to the Eurographics Association.* 20 | 21 | ## License ## 22 | 23 | Copyright (C) 2017-2019 Valentin Bruder vbruder@gmail.com 24 | 25 | This software is licensed under [LGPLv3+](https://www.gnu.org/licenses/lgpl-3.0.en.html). 26 | 27 | ## Credits ## 28 | 29 | * Color wheel from Mattia Basaglia's Qt-Color-Widgets: https://github.com/mbasaglia/Qt-Color-Widgets 30 | * OpenCL utils based on Erik Smistad's OpenCLUtilityLibrary: https://github.com/smistad/OpenCLUtilityLibrary 31 | * Transfer function editor based on Qt sample code. 32 | * Stippling is based on O. Deussen, M. Spicker, Q. Zeng's [Weighted Linde-Buzo-Gray Algorithm](http://graphics.uni-konstanz.de/publikationen/Deussen2017LindeBuzoGray/index.html) 33 | -------------------------------------------------------------------------------- /inc/TOBIIRESEARCH/tobii_research.h: -------------------------------------------------------------------------------- 1 | /** 2 | @copyright COPYRIGHT 2017 - PROPERTY OF TOBII AB 3 | @copyright 2017 TOBII AB - KARLSROVAGEN 2D, DANDERYD 182 53, SWEDEN - All Rights Reserved. 4 | 5 | @copyright NOTICE: All information contained herein is, and remains, the property of Tobii AB and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to Tobii AB and its suppliers and may be covered by U.S.and Foreign Patents, patent applications, and are protected by trade secret or copyright law. Dissemination of this information or reproduction of this material is strictly forbidden unless prior written permission is obtained from Tobii AB. 6 | */ 7 | 8 | /** 9 | * @file tobii_research.h 10 | * @brief Generic SDK functions. 11 | * 12 | */ 13 | 14 | #ifndef TOBII_RESEARCH_H_ 15 | #define TOBII_RESEARCH_H_ 16 | 17 | #ifdef __cplusplus 18 | extern "C" { 19 | #endif 20 | 21 | #include 22 | #include 23 | 24 | #if _WIN32 || _WIN64 25 | #ifdef TOBII_STATIC_LIB 26 | #define TOBII_RESEARCH_CALL 27 | #define TOBII_RESEARCH_API 28 | #else 29 | #define TOBII_RESEARCH_CALL __cdecl 30 | #ifdef TOBII_EXPORTING 31 | #define TOBII_RESEARCH_API __declspec(dllexport) 32 | #else 33 | #define TOBII_RESEARCH_API __declspec(dllimport) 34 | #endif /* TOBII_EXPORTING */ 35 | #endif /* TOBII_STATIC_LIB */ 36 | #else 37 | #define TOBII_RESEARCH_API 38 | #define TOBII_RESEARCH_CALL 39 | #endif /* _WIN32 */ 40 | 41 | 42 | /** 43 | Status codes returned by the SDK. 44 | */ 45 | typedef enum { 46 | /** 47 | No error. 48 | */ 49 | TOBII_RESEARCH_STATUS_OK, 50 | 51 | /** 52 | Fatal error. This should normally not happen. 53 | */ 54 | TOBII_RESEARCH_STATUS_FATAL_ERROR, 55 | 56 | /** 57 | Failed to initialize the API. This is a fatal error. 58 | */ 59 | TOBII_RESEARCH_STATUS_INITIALIZE_FAILED, 60 | 61 | /** 62 | Failed to terminate the API. 63 | */ 64 | TOBII_RESEARCH_STATUS_TERMINATE_FAILED, 65 | 66 | /** 67 | Failed to create browser for finding local devices. 68 | */ 69 | TOBII_RESEARCH_STATUS_LOCALBROWSER_CREATE_FAILED, 70 | 71 | /** 72 | Failed to poll local devices. 73 | */ 74 | TOBII_RESEARCH_STATUS_LOCALBROWSER_POLL_FAILED, 75 | 76 | /** 77 | Failed to create zero configuration browser. 78 | */ 79 | TOBII_RESEARCH_STATUS_ZEROCONFBROWSER_CREATE_FAILED, 80 | 81 | /** 82 | Failed to poll devices from zero configuration browser. 83 | */ 84 | TOBII_RESEARCH_STATUS_ZEROCONFBROWSER_POLL_FAILED, 85 | 86 | /** 87 | Failed to create browser that looks for devices in file. 88 | */ 89 | TOBII_RESEARCH_STATUS_FILEBROWSER_CREATE_FAILED, 90 | 91 | /** 92 | Failed to poll devices from file browser. 93 | */ 94 | TOBII_RESEARCH_STATUS_FILEBROWSER_POLL_FAILED, 95 | 96 | /** 97 | An invalid parameter was given to the method. 98 | */ 99 | TOBII_RESEARCH_STATUS_INVALID_PARAMETER, 100 | 101 | /** 102 | The operation was invalid. 103 | */ 104 | TOBII_RESEARCH_STATUS_INVALID_OPERATION, 105 | 106 | /** 107 | Internal core error code. Should never be returned by the SDK. 108 | */ 109 | TOBII_RESEARCH_STATUS_UNINITIALIZED, 110 | 111 | /** 112 | A parameter is out of bounds. 113 | */ 114 | TOBII_RESEARCH_STATUS_OUT_OF_BOUNDS, 115 | 116 | /** 117 | The display area is not valid. Please configure the eye tracker. 118 | */ 119 | TOBII_RESEARCH_STATUS_DISPLAY_AREA_NOT_VALID, 120 | 121 | /** 122 | The buffer is too small. 123 | */ 124 | TOBII_RESEARCH_STATUS_BUFFER_TOO_SMALL, 125 | 126 | /** 127 | tobii_research_initialize has not been called. 128 | */ 129 | TOBII_RESEARCH_STATUS_NOT_INITIALIZED, 130 | 131 | /** 132 | tobii_research_initialize has already been called. 133 | */ 134 | TOBII_RESEARCH_STATUS_ALREADY_INITIALIZED, 135 | 136 | /** 137 | The license saved on the device failed to apply when connecting. It has probably expired. 138 | */ 139 | TOBII_RESEARCH_STATUS_SAVED_LICENSE_FAILED_TO_APPLY, 140 | 141 | /** 142 | Internal stream engine error. 143 | */ 144 | TOBII_RESEARCH_STATUS_SE_INTERNAL = 200, 145 | 146 | /** 147 | The operation requires a higher license type. 148 | */ 149 | TOBII_RESEARCH_STATUS_SE_INSUFFICIENT_LICENSE, 150 | 151 | /** 152 | The operations isn't supported in the current context. 153 | */ 154 | TOBII_RESEARCH_STATUS_SE_NOT_SUPPORTED, 155 | 156 | /** 157 | The device is unavailable. 158 | */ 159 | TOBII_RESEARCH_STATUS_SE_NOT_AVAILABLE, 160 | 161 | /** 162 | Connection to the device failed. 163 | */ 164 | TOBII_RESEARCH_STATUS_SE_CONNECTION_FAILED, 165 | 166 | /** 167 | The operation timed out. 168 | */ 169 | TOBII_RESEARCH_STATUS_SE_TIMED_OUT, 170 | 171 | /** 172 | Failed to allocate memory. 173 | */ 174 | TOBII_RESEARCH_STATUS_SE_ALLOCATION_FAILED, 175 | 176 | /** 177 | The API is already initialized. 178 | */ 179 | TOBII_RESEARCH_STATUS_SE_ALREADY_INITIALIZED, 180 | 181 | /** 182 | The API isn't initialized. 183 | */ 184 | TOBII_RESEARCH_STATUS_SE_NOT_INITIALIZED, 185 | 186 | /** 187 | An invalid parameter was given to the method. 188 | */ 189 | TOBII_RESEARCH_STATUS_SE_INVALID_PARAMETER, 190 | 191 | /** 192 | Calibration has already started. 193 | */ 194 | TOBII_RESEARCH_STATUS_SE_CALIBRATION_ALREADY_STARTED, 195 | 196 | /** 197 | Calibration isn't started. 198 | */ 199 | TOBII_RESEARCH_STATUS_SE_CALIBRATION_NOT_STARTED, 200 | 201 | /** 202 | Already subscribed. 203 | */ 204 | TOBII_RESEARCH_STATUS_SE_ALREADY_SUBSCRIBED, 205 | 206 | /** 207 | Not subscribed. 208 | */ 209 | TOBII_RESEARCH_STATUS_SE_NOT_SUBSCRIBED, 210 | 211 | /** 212 | Operation failed. 213 | */ 214 | TOBII_RESEARCH_STATUS_SE_OPERATION_FAILED, 215 | 216 | /** 217 | Conflicting api instances. 218 | */ 219 | TOBII_RESEARCH_STATUS_SE_CONFLICTING_API_INSTANCES, 220 | 221 | /** 222 | Calibration busy. 223 | */ 224 | TOBII_RESEARCH_STATUS_SE_CALIBRATION_BUSY, 225 | 226 | /** 227 | Callback in progress. 228 | */ 229 | TOBII_RESEARCH_STATUS_SE_CALLBACK_IN_PROGRESS, 230 | 231 | /** 232 | Too many users subscribed to a stream. 233 | */ 234 | TOBII_RESEARCH_STATUS_SE_TOO_MANY_SUBSCRIBERS, 235 | 236 | /** 237 | The buffer is too small. 238 | */ 239 | TOBII_RESEARCH_STATUS_SE_BUFFER_TOO_SMALL, 240 | 241 | /** 242 | No response from firmware. 243 | */ 244 | TOBII_RESEARCH_STATUS_SE_FIRMWARE_NO_RESPONSE, 245 | 246 | /** 247 | Internal error. 248 | */ 249 | TOBII_RESEARCH_STATUS_FWUPGRADE_INTERNAL = 400, 250 | 251 | /** 252 | Firmware upgrade is not supported. 253 | */ 254 | TOBII_RESEARCH_STATUS_FWUPGRADE_NOT_SUPPORTED, 255 | 256 | /** 257 | Unknown firmware version. 258 | */ 259 | TOBII_RESEARCH_STATUS_FWUPGRADE_UNKNOWN_FIRMWARE_VERSION, 260 | 261 | /** 262 | Connection failed. 263 | */ 264 | TOBII_RESEARCH_STATUS_FWUPGRADE_CONNECTION_FAILED, 265 | 266 | /** 267 | Invalid parameter. 268 | */ 269 | TOBII_RESEARCH_STATUS_FWUPGRADE_INVALID_PARAMETER, 270 | 271 | /** 272 | Device mismatch. The firmware package is not meant for the device. 273 | */ 274 | TOBII_RESEARCH_STATUS_FWUPGRADE_PACKAGE_DEVICE_MISMATCH, 275 | 276 | /** 277 | Parse response. 278 | */ 279 | TOBII_RESEARCH_STATUS_FWUPGRADE_PARSE_RESPONSE, 280 | 281 | /** 282 | The firmware upgrade operation failed. 283 | */ 284 | TOBII_RESEARCH_STATUS_FWUPGRADE_OPERATION_FAILED, 285 | 286 | /** 287 | Memory allocation failed during firmware upgrade. 288 | */ 289 | TOBII_RESEARCH_STATUS_FWUPGRADE_ALLOCATION_FAILED, 290 | 291 | /** 292 | The firmware failed to respond during firmware upgrade. 293 | */ 294 | TOBII_RESEARCH_STATUS_FWUPGRADE_FIRMWARE_NO_RESPONSE, 295 | 296 | /** 297 | The firmware downgrade operation is not supported. 298 | */ 299 | TOBII_RESEARCH_STATUS_FWUPGRADE_DOWNGRADE_NOT_SUPPORTED, 300 | 301 | /** 302 | Unknown error. This is a fatal error. 303 | */ 304 | TOBII_RESEARCH_STATUS_UNKNOWN = 1000 305 | } TobiiResearchStatus; 306 | 307 | /** 308 | Opaque representation of an eye tracker struct. 309 | */ 310 | typedef struct TobiiResearchEyeTracker TobiiResearchEyeTracker; 311 | 312 | /** 313 | Contains all eye trackers connected to the computer or the network. 314 | */ 315 | typedef struct { 316 | /** 317 | An array of pointers to eye trackers. 318 | */ 319 | TobiiResearchEyeTracker** eyetrackers; 320 | /** 321 | Number of eye tracker pointers in the array. 322 | */ 323 | size_t count; 324 | } TobiiResearchEyeTrackers; 325 | 326 | /** 327 | Source of log message. 328 | */ 329 | typedef enum { 330 | /** 331 | The log message is from stream engine. 332 | */ 333 | TOBII_RESEARCH_LOG_SOURCE_STREAM_ENGINE, 334 | 335 | /** 336 | The log message is from the SDK. 337 | */ 338 | TOBII_RESEARCH_LOG_SOURCE_SDK, 339 | 340 | /** 341 | The log message is from the firmware upgrade module. 342 | */ 343 | TOBII_RESEARCH_LOG_SOURCE_FIRMWARE_UPGRADE 344 | } TobiiResearchLogSource; 345 | 346 | /** 347 | Log level. 348 | */ 349 | typedef enum { 350 | /** 351 | Error message. 352 | */ 353 | TOBII_RESEARCH_LOG_LEVEL_ERROR, 354 | 355 | /** 356 | Warning message. 357 | */ 358 | TOBII_RESEARCH_LOG_LEVEL_WARNING, 359 | 360 | /** 361 | Information message. 362 | */ 363 | TOBII_RESEARCH_LOG_LEVEL_INFORMATION, 364 | 365 | /** 366 | Debug message. 367 | */ 368 | TOBII_RESEARCH_LOG_LEVEL_DEBUG, 369 | 370 | /** 371 | Trace message. 372 | */ 373 | TOBII_RESEARCH_LOG_LEVEL_TRACE 374 | } TobiiResearchLogLevel; 375 | 376 | /** 377 | Represents a normalized x- and y-coordinate point in a two-dimensional plane. 378 | */ 379 | typedef struct { 380 | /** 381 | Position of the point in the X axis. 382 | */ 383 | float x; 384 | /** 385 | Position of the point in the Y axis. 386 | */ 387 | float y; 388 | } TobiiResearchNormalizedPoint2D; 389 | 390 | 391 | /** 392 | Represents an x-, y- and z-coordinate point in a three-dimensional space. 393 | */ 394 | typedef struct { 395 | /** 396 | Position of the point in the X axis. 397 | */ 398 | float x; 399 | /** 400 | Position of the point in the Y axis. 401 | */ 402 | float y; 403 | /** 404 | Position of the point in the Z axis. 405 | */ 406 | float z; 407 | } TobiiResearchPoint3D; 408 | 409 | /** 410 | Represents a normalized x-, y- and z-coordinate point in a three-dimensional space. 411 | */ 412 | typedef TobiiResearchPoint3D TobiiResearchNormalizedPoint3D; 413 | 414 | /** 415 | @brief Log callback. 416 | 417 | Implement this and send as a parameter to @ref tobii_research_logging_subscribe. 418 | @param system_time_stamp: The time stamp according to the computer's internal clock. 419 | @param source: Source of log message. 420 | @param level: Log message level. 421 | @param message: The log message. 422 | */ 423 | typedef void(*tobii_research_log_callback)(int64_t system_time_stamp, 424 | TobiiResearchLogSource source, 425 | TobiiResearchLogLevel level, 426 | const char* message); 427 | 428 | /** 429 | @brief Subscribes to logging. 430 | 431 | @param callback: Callback that will receive log messages. 432 | @returns A @ref TobiiResearchStatus code. 433 | */ 434 | TOBII_RESEARCH_API TobiiResearchStatus TOBII_RESEARCH_CALL tobii_research_logging_subscribe( 435 | tobii_research_log_callback callback); 436 | 437 | /** 438 | @brief Unsubscribes from logging. 439 | 440 | @returns A @ref TobiiResearchStatus code. 441 | */ 442 | TOBII_RESEARCH_API TobiiResearchStatus TOBII_RESEARCH_CALL tobii_research_logging_unsubscribe(); 443 | 444 | /** 445 | @brief Finds eye trackers connected to the computer or the network. 446 | 447 | The eye trackers can be used in any tobii_research function that requires an eye tracker. 448 | 449 | \snippet find_all_eyetrackers.c FindAllEyetrackers 450 | 451 | @param eyetrackers: Pointers to found eye trackers will be stored in this struct. 452 | @returns A @ref TobiiResearchStatus code. 453 | */ 454 | TOBII_RESEARCH_API TobiiResearchStatus TOBII_RESEARCH_CALL tobii_research_find_all_eyetrackers( 455 | TobiiResearchEyeTrackers** eyetrackers); 456 | 457 | /** 458 | @brief Free memory allocation for the result received via @ref tobii_research_find_all_eyetrackers. 459 | 460 | @param eyetrackers: Eye trackers to free. 461 | */ 462 | TOBII_RESEARCH_API void TOBII_RESEARCH_CALL tobii_research_free_eyetrackers( 463 | TobiiResearchEyeTrackers* eyetrackers); 464 | 465 | /** 466 | @brief Gets data for an eye tracker given an address. 467 | 468 | \snippet create_eyetracker.c Example 469 | 470 | @param address: Address of eye tracker to get data for. 471 | @param eyetracker: Eye tracker object returned. 472 | @returns A @ref TobiiResearchStatus code. 473 | */ 474 | TOBII_RESEARCH_API TobiiResearchStatus TOBII_RESEARCH_CALL tobii_research_get_eyetracker( 475 | const char* address, 476 | TobiiResearchEyeTracker** eyetracker); 477 | 478 | /** 479 | @brief Retrieves the time stamp from the system clock in microseconds. 480 | 481 | \snippet get_system_time_stamp.c Example 482 | 483 | @param time_stamp_us: The time stamp of the system in microseconds. 484 | @returns A @ref TobiiResearchStatus code. 485 | */ 486 | TOBII_RESEARCH_API TobiiResearchStatus TOBII_RESEARCH_CALL tobii_research_get_system_time_stamp(int64_t* time_stamp_us); 487 | 488 | /** 489 | @brief Free memory allocation for a string allocated by the SDK. 490 | 491 | @param str: String to free. 492 | */ 493 | TOBII_RESEARCH_API void TOBII_RESEARCH_CALL tobii_research_free_string(char* str); 494 | 495 | 496 | /** 497 | SDK Version. 498 | */ 499 | typedef struct { 500 | /** 501 | Major. 502 | */ 503 | int major; 504 | /** 505 | Minor. 506 | */ 507 | int minor; 508 | /** 509 | Revision. 510 | */ 511 | int revision; 512 | /** 513 | Build. 514 | */ 515 | int build; 516 | } TobiiResearchSDKVersion; 517 | 518 | /** 519 | Gets the SDK version. 520 | 521 | @param sdk_version: Version of the SDK. 522 | @returns A TobiiResearchStatus code. 523 | */ 524 | TOBII_RESEARCH_API TobiiResearchStatus TOBII_RESEARCH_CALL tobii_research_get_sdk_version( 525 | TobiiResearchSDKVersion* sdk_version); 526 | 527 | #ifdef __cplusplus 528 | } 529 | #endif 530 | #endif /* TOBII_RESEARCH_H_ */ 531 | -------------------------------------------------------------------------------- /inc/TOBIIRESEARCH/tobii_research_calibration.h: -------------------------------------------------------------------------------- 1 | /** 2 | @copyright COPYRIGHT 2017 - PROPERTY OF TOBII AB 3 | @copyright 2017 TOBII AB - KARLSROVAGEN 2D, DANDERYD 182 53, SWEDEN - All Rights Reserved. 4 | 5 | @copyright NOTICE: All information contained herein is, and remains, the property of Tobii AB and its suppliers, if any. The intellectual and technical concepts contained herein are proprietary to Tobii AB and its suppliers and may be covered by U.S.and Foreign Patents, patent applications, and are protected by trade secret or copyright law. Dissemination of this information or reproduction of this material is strictly forbidden unless prior written permission is obtained from Tobii AB. 6 | */ 7 | 8 | /** 9 | * @file tobii_research_calibration.h 10 | * @brief Functionality for implementing calibration. 11 | * 12 | */ 13 | 14 | #ifndef TOBII_RESEARCH_CALIBRATION_H_ 15 | #define TOBII_RESEARCH_CALIBRATION_H_ 16 | 17 | #include "tobii_research.h" 18 | 19 | #ifdef __cplusplus 20 | extern "C" { 21 | #endif 22 | 23 | /** 24 | "Defines the overall status of a calibration process. 25 | */ 26 | typedef enum { 27 | /** 28 | Indicates that the calibration process failed. 29 | */ 30 | TOBII_RESEARCH_CALIBRATION_FAILURE = 0, 31 | /** 32 | Indicates that the calibration process succeeded for both eyes. 33 | */ 34 | TOBII_RESEARCH_CALIBRATION_SUCCESS = 1, 35 | /** 36 | Indicates that the left eye calibration process succeeded. 37 | */ 38 | TOBII_RESEARCH_CALIBRATION_SUCCESS_LEFT_EYE = 2, 39 | /** 40 | Indicates that the right eye calibration process succeeded. 41 | */ 42 | TOBII_RESEARCH_CALIBRATION_SUCCESS_RIGHT_EYE = 3 43 | } TobiiResearchCalibrationStatus; 44 | 45 | /** 46 | Defines the selected eye. 47 | */ 48 | typedef enum TobiiResearchSelectedEye { 49 | /** 50 | Left Eye 51 | */ 52 | TOBII_RESEARCH_SELECTED_EYE_LEFT, 53 | 54 | /** 55 | Right Eye 56 | */ 57 | TOBII_RESEARCH_SELECTED_EYE_RIGHT, 58 | 59 | /** 60 | Both Eyes 61 | */ 62 | TOBII_RESEARCH_SELECTED_EYE_BOTH 63 | } TobiiResearchSelectedEye; 64 | 65 | /** 66 | Defines the validity of calibration eye data. 67 | */ 68 | typedef enum { 69 | /** 70 | The eye tracking failed or the calibration eye data is invalid. 71 | */ 72 | TOBII_RESEARCH_CALIBRATION_EYE_VALIDITY_INVALID_AND_NOT_USED = -1, 73 | 74 | /** 75 | Eye Tracking was successful, but the calibration eye data was not used in calibration e.g. gaze was too far away. 76 | */ 77 | TOBII_RESEARCH_CALIBRATION_EYE_VALIDITY_VALID_BUT_NOT_USED = 0, 78 | 79 | /** 80 | The calibration eye data was valid and used in calibration. 81 | */ 82 | TOBII_RESEARCH_CALIBRATION_EYE_VALIDITY_VALID_AND_USED = 1, 83 | 84 | /** 85 | The calibration eye data has an unexpected validity. 86 | */ 87 | TOBII_RESEARCH_CALIBRATION_EYE_VALIDITY_UNKNOWN 88 | } TobiiResearchCalibrationEyeValidity; 89 | 90 | /** 91 | Represents the calibration sample data collected for one eye. 92 | */ 93 | typedef struct { 94 | /** 95 | The eye sample position on the active display Area for the left eye. 96 | */ 97 | TobiiResearchNormalizedPoint2D position_on_display_area; 98 | /** 99 | Information about if the sample was used or not in the calculation for the left eye. 100 | */ 101 | TobiiResearchCalibrationEyeValidity validity; 102 | } TobiiResearchCalibrationEyeData; 103 | 104 | /** 105 | Represents the data collected for a calibration sample. 106 | */ 107 | typedef struct { 108 | /** 109 | The calibration sample data for the left eye. 110 | */ 111 | TobiiResearchCalibrationEyeData left_eye; 112 | /** 113 | The calibration sample data for the right eye. 114 | */ 115 | TobiiResearchCalibrationEyeData right_eye; 116 | } TobiiResearchCalibrationSample; 117 | 118 | /** 119 | Represents the Calibration Point and its collected calibration samples. 120 | */ 121 | typedef struct { 122 | /** 123 | The position of the calibration point in the Active Display Area. 124 | */ 125 | TobiiResearchNormalizedPoint2D position_on_display_area; 126 | /** 127 | An array of calibration samples. 128 | */ 129 | TobiiResearchCalibrationSample* calibration_samples; 130 | /** 131 | Number of calibration calibration points in the array. 132 | */ 133 | size_t calibration_sample_count; 134 | } TobiiResearchCalibrationPoint; 135 | 136 | /** 137 | Represents the result of the calculated calibration. 138 | */ 139 | typedef struct { 140 | /** 141 | Array of calibration points. 142 | */ 143 | TobiiResearchCalibrationPoint* calibration_points; 144 | /** 145 | Number of calibration calibration points in the array. 146 | */ 147 | size_t calibration_point_count; 148 | /** 149 | Gets the status of the calculation. 150 | */ 151 | TobiiResearchCalibrationStatus status; 152 | } TobiiResearchCalibrationResult; 153 | 154 | 155 | /** 156 | Represents the result of the calculated HMD based calibration. 157 | */ 158 | typedef struct { 159 | /** 160 | Gets the status of the calculation. 161 | */ 162 | TobiiResearchCalibrationStatus status; 163 | } TobiiResearchHMDCalibrationResult; 164 | 165 | /** 166 | @brief Enters the screen based calibration mode and the eye tracker is made ready for collecting data and calculating new calibrations. 167 | 168 | \snippet calibration.c CalibrationEnterExample 169 | 170 | @param eyetracker: Eye tracker object. 171 | @returns A @ref TobiiResearchStatus code. 172 | */ 173 | TOBII_RESEARCH_API TobiiResearchStatus TOBII_RESEARCH_CALL 174 | tobii_research_screen_based_calibration_enter_calibration_mode( 175 | TobiiResearchEyeTracker* eyetracker); 176 | 177 | /** 178 | @brief Leaves the screen based calibration mode. 179 | 180 | \snippet calibration.c CalibrationLeftExample 181 | 182 | @param eyetracker: Eye tracker object. 183 | @returns A @ref TobiiResearchStatus code. 184 | */ 185 | TOBII_RESEARCH_API TobiiResearchStatus TOBII_RESEARCH_CALL 186 | tobii_research_screen_based_calibration_leave_calibration_mode( 187 | TobiiResearchEyeTracker* eyetracker); 188 | 189 | /** 190 | @brief Starts collecting data for a calibration point. 191 | 192 | The argument used is the point the calibration user is assumed to 193 | be looking at and is given in the active display area coordinate system. 194 | 195 | You must call tobii_research_calibration_enter_calibration_mode before calling this function. 196 | This function is blocking while collecting data and may take up to 10 seconds. 197 | 198 | \snippet calibration.c CalibrationExample 199 | 200 | @param eyetracker: Eye tracker object. 201 | @param x: Normalized x coordinate on active display area where the user is looking. 202 | @param y: Normalized y coordinate on active display area where the user is looking. 203 | @returns A @ref TobiiResearchStatus code. 204 | */ 205 | TOBII_RESEARCH_API TobiiResearchStatus TOBII_RESEARCH_CALL tobii_research_screen_based_calibration_collect_data( 206 | TobiiResearchEyeTracker* eyetracker, 207 | float x, 208 | float y); 209 | 210 | /** 211 | @brief Removes the collected data associated with a specific calibration point. 212 | 213 | \snippet calibration.c ReCalibrationExample 214 | 215 | @param eyetracker: Eye tracker object. 216 | @param x: Normalized x coordinate of point to discard. 217 | @param y: Normalized y coordinate of point to discard. 218 | @returns A @ref TobiiResearchStatus code. 219 | */ 220 | TOBII_RESEARCH_API TobiiResearchStatus TOBII_RESEARCH_CALL tobii_research_screen_based_calibration_discard_data( 221 | TobiiResearchEyeTracker* eyetracker, 222 | float x, 223 | float y); 224 | 225 | /** 226 | @brief Uses the collected data and tries to compute calibration parameters. 227 | 228 | If the calculation is successful, the result is applied to the eye tracker. 229 | If there is insufficient data to compute a new calibration or if the collected 230 | data is not good enough then calibration is failed and will not be applied. 231 | 232 | \snippet calibration.c CalibrationExample 233 | 234 | @param eyetracker: Eye tracker object. 235 | @param result: Represents the result of the calculated calibration. 236 | @returns A @ref TobiiResearchStatus code. 237 | */ 238 | TOBII_RESEARCH_API TobiiResearchStatus TOBII_RESEARCH_CALL tobii_research_screen_based_calibration_compute_and_apply( 239 | TobiiResearchEyeTracker* eyetracker, 240 | TobiiResearchCalibrationResult** result); 241 | 242 | /** 243 | @brief Free memory allocation for the calibration result received via @ref tobii_research_screen_based_calibration_compute_and_apply. 244 | 245 | @param result: Calibration result to free. 246 | */ 247 | TOBII_RESEARCH_API void TOBII_RESEARCH_CALL tobii_research_free_screen_based_calibration_result( 248 | TobiiResearchCalibrationResult* result); 249 | 250 | /** 251 | @brief Starts collecting data for a calibration point. 252 | 253 | The argument used is the point the calibration user is assumed to 254 | be looking at and is given in the active display area coordinate system. 255 | 256 | You must call tobii_research_calibration_enter_calibration_mode before calling this function. 257 | This function is blocking while collecting data and may take up to 10 seconds. 258 | 259 | @param eyetracker: Eye tracker object. 260 | @param x: Normalized x coordinate on active display area where the user is looking. 261 | @param y: Normalized y coordinate on active display area where the user is looking. 262 | @param eye_to_calibrate: TobiiResearchSelectedEye instance that selects for which eye to collect data for the monocular calibration. 263 | @param collected_eyes: TobiiResearchSelectedEye instance that indicates for which eyes data was collected for the monocular calibration. 264 | @returns A @ref TobiiResearchStatus code. 265 | */ 266 | TOBII_RESEARCH_API TobiiResearchStatus TOBII_RESEARCH_CALL 267 | tobii_research_screen_based_monocular_calibration_collect_data(TobiiResearchEyeTracker* eyetracker, 268 | float x, float y, TobiiResearchSelectedEye eye_to_calibrate, TobiiResearchSelectedEye* collected_eyes); 269 | 270 | /** 271 | @brief Removes the collected data associated with a specific calibration point. 272 | 273 | @param eyetracker: Eye tracker object. 274 | @param x: Normalized x coordinate of point to discard. 275 | @param y: Normalized y coordinate of point to discard. 276 | @param eye_to_calibrate: TobiiResearchSelectedEye instance that selects for which eye to discard data for the monocular calibration. 277 | @returns A @ref TobiiResearchStatus code. 278 | */ 279 | TOBII_RESEARCH_API TobiiResearchStatus TOBII_RESEARCH_CALL 280 | tobii_research_screen_based_monocular_calibration_discard_data(TobiiResearchEyeTracker* eyetracker, 281 | float x, float y, TobiiResearchSelectedEye eye_to_calibrate); 282 | /** 283 | @brief Uses the collected data and tries to compute calibration parameters. 284 | 285 | If the calculation is successful, the result is 286 | applied to the eye tracker. If there is insufficient data to compute a new calibration or if the collected data is 287 | not good enough then calibration is failed and will not be applied. 288 | 289 | @param eyetracker: Eye tracker object. 290 | @param calibrated_eyes: TobiiResearchSelectedEye instance with the calibrated eyes in the monocular calibration. 291 | @returns A @ref TobiiResearchStatus code. 292 | */ 293 | TOBII_RESEARCH_API TobiiResearchStatus TOBII_RESEARCH_CALL 294 | tobii_research_screen_based_monocular_calibration_compute_and_apply(TobiiResearchEyeTracker* eyetracker, 295 | TobiiResearchCalibrationResult** result); 296 | 297 | /** 298 | @brief Enters the hmd based calibration mode and the eye tracker is made ready for collecting data and calculating new calibrations. 299 | 300 | \snippet hmd_calibration.c HMDCalibrationEnterExample 301 | 302 | @param eyetracker: Eye tracker object. 303 | @returns A @ref TobiiResearchStatus code. 304 | */ 305 | TOBII_RESEARCH_API TobiiResearchStatus TOBII_RESEARCH_CALL 306 | tobii_research_hmd_based_calibration_enter_calibration_mode( 307 | TobiiResearchEyeTracker* eyetracker); 308 | 309 | /** 310 | @brief Leaves the hmd based calibration mode. 311 | 312 | \snippet hmd_calibration.c HMDCalibrationLeftExample 313 | 314 | @param eyetracker: Eye tracker object. 315 | @returns A @ref TobiiResearchStatus code. 316 | */ 317 | TOBII_RESEARCH_API TobiiResearchStatus TOBII_RESEARCH_CALL 318 | tobii_research_hmd_based_calibration_leave_calibration_mode( 319 | TobiiResearchEyeTracker* eyetracker); 320 | 321 | /** 322 | @brief Starts collecting data for a calibration point. 323 | 324 | The argument used is the point the calibration user is assumed to be looking at and is given in the HMD coordinate system. 325 | 326 | You must call tobii_research_screen_based_calibration_enter_calibration_mode before calling this function. 327 | This function is blocking while collecting data and may take up to 10 seconds. 328 | 329 | @param eyetracker: Eye tracker object. 330 | @param x: x coordinate in the HMD coordinate system where the user is looking. 331 | @param y: y coordinate in the HMD coordinate system where the user is looking. 332 | @param z: z coordinate in the HMD coordinate system where the user is looking. 333 | 334 | @returns A @ref TobiiResearchStatus code. 335 | */ 336 | TOBII_RESEARCH_API TobiiResearchStatus TOBII_RESEARCH_CALL tobii_research_hmd_based_calibration_collect_data( 337 | TobiiResearchEyeTracker* eyetracker, float x, float y, float z); 338 | 339 | 340 | /** 341 | @brief Uses the collected data and tries to compute calibration parameters. 342 | 343 | If the calculation is successful, the result is applied to the eye tracker. 344 | If there is insufficient data to compute a new calibration or if the collected 345 | data is not good enough then calibration is failed and will not be applied. 346 | 347 | \snippet hmd_calibration.c HMDCalibrationExample 348 | 349 | @param eyetracker: Eye tracker object. 350 | @param result: Represents the result of the calculated HMD calibration. 351 | @returns A @ref TobiiResearchStatus code. 352 | */ 353 | TOBII_RESEARCH_API TobiiResearchStatus TOBII_RESEARCH_CALL tobii_research_hmd_based_calibration_compute_and_apply( 354 | TobiiResearchEyeTracker* eyetracker, 355 | TobiiResearchHMDCalibrationResult* result); 356 | 357 | #ifdef __cplusplus 358 | } 359 | #endif 360 | #endif /* TOBII_RESEARCH_CALIBRATION_H_ */ 361 | -------------------------------------------------------------------------------- /lbg-stippling/.clang-format: -------------------------------------------------------------------------------- 1 | # Basic style 2 | BasedOnStyle: LLVM 3 | IndentWidth: 4 4 | PointerAlignment: Left 5 | ColumnLimit: 100 6 | 7 | AlignOperands: true 8 | AllowShortFunctionsOnASingleLine: Inline 9 | AllowShortBlocksOnASingleLine: true 10 | AllowShortLoopsOnASingleLine: true 11 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 12 | BinPackParameters: false 13 | 14 | # Comments 15 | AlwaysBreakTemplateDeclarations: true 16 | AlignTrailingComments: true 17 | SpacesBeforeTrailingComments: 1 18 | -------------------------------------------------------------------------------- /lbg-stippling/.gitignore: -------------------------------------------------------------------------------- 1 | /CMakeLists.txt.user 2 | /build 3 | /.vscode -------------------------------------------------------------------------------- /lbg-stippling/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(LBGSampling) 4 | 5 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 6 | set(CMAKE_AUTOMOC ON) 7 | set(CMAKE_AUTORCC ON) 8 | set(CMAKE_CXX_STANDARD 17) 9 | 10 | set(PROJECT_DIR ${PROJECT_SOURCE_DIR}) 11 | 12 | # add headers to project 13 | set(HEADERS 14 | ${PROJECT_DIR}/src/mainwindow.h 15 | ${PROJECT_DIR}/src/stippleviewer.h 16 | ${PROJECT_DIR}/src/voronoidiagram.h 17 | ${PROJECT_DIR}/src/voronoicell.h 18 | ${PROJECT_DIR}/src/lbgstippling.h 19 | ${PROJECT_DIR}/src/settingswidget.h 20 | ${PROJECT_DIR}/src/nanoflann.hpp 21 | ) 22 | 23 | # add sources to project 24 | set(SOURCES 25 | ${PROJECT_DIR}/main.cpp 26 | ${PROJECT_DIR}/src/mainwindow.cpp 27 | ${PROJECT_DIR}/src/stippleviewer.cpp 28 | ${PROJECT_DIR}/src/voronoidiagram.cpp 29 | ${PROJECT_DIR}/src/lbgstippling.cpp 30 | ${PROJECT_DIR}/src/settingswidget.cpp 31 | ${PROJECT_DIR}/src/voronoicell.cpp 32 | ) 33 | 34 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}/src) 35 | 36 | find_package(Qt5 COMPONENTS Core Widgets Svg PrintSupport REQUIRED) 37 | 38 | find_package(OpenMP) 39 | if (OPENMP_FOUND) 40 | set (CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}") 41 | set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}") 42 | set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} ${OpenMP_EXE_LINKER_FLAGS}") 43 | endif() 44 | 45 | add_executable(${PROJECT_NAME} ${HEADERS} ${SOURCES} resources.qrc) 46 | 47 | target_link_libraries(${PROJECT_NAME} 48 | Qt5::Core 49 | Qt5::Widgets 50 | Qt5::Svg 51 | Qt5::PrintSupport 52 | ) 53 | 54 | # add dlls to runtime 55 | configure_file("${PROJECT_DIR}/dlls/Qt5Gui.dll" "${PROJECT_DIR}/build/Release/Qt5Gui.dll" COPYONLY) 56 | configure_file("${PROJECT_DIR}/dlls/Qt5Guid.dll" "${PROJECT_DIR}/build/Debug/Qt5Guid.dll" COPYONLY) 57 | 58 | configure_file("${PROJECT_DIR}/dlls/Qt5PrintSupport.dll" "${PROJECT_DIR}/build/Release/Qt5PrintSupport.dll" COPYONLY) 59 | configure_file("${PROJECT_DIR}/dlls/Qt5PrintSupportd.dll" "${PROJECT_DIR}/build/Debug/Qt5PrintSupportd.dll" COPYONLY) 60 | 61 | configure_file("${PROJECT_DIR}/dlls/Qt5Svg.dll" "${PROJECT_DIR}/build/Release/Qt5Svg.dll" COPYONLY) 62 | configure_file("${PROJECT_DIR}/dlls/Qt5Svgd.dll" "${PROJECT_DIR}/build/Debug/Qt5Svgd.dll" COPYONLY) 63 | 64 | configure_file("${PROJECT_DIR}/dlls/Qt5Widgets.dll" "${PROJECT_DIR}/build/Release/Qt5Widgets.dll" COPYONLY) 65 | configure_file("${PROJECT_DIR}/dlls/Qt5Widgetsd.dll" "${PROJECT_DIR}/build/Debug/Qt5Widgetsd.dll" COPYONLY) 66 | 67 | configure_file("${PROJECT_DIR}/dlls/Qt5Core.dll" "${PROJECT_DIR}/build/Release/Qt5Core.dll" COPYONLY) 68 | configure_file("${PROJECT_DIR}/dlls/Qt5Cored.dll" "${PROJECT_DIR}/build/Debug/Qt5Cored.dll" COPYONLY) 69 | -------------------------------------------------------------------------------- /lbg-stippling/LICENSE: -------------------------------------------------------------------------------- 1 | GNU LESSER GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | 9 | This version of the GNU Lesser General Public License incorporates 10 | the terms and conditions of version 3 of the GNU General Public 11 | License, supplemented by the additional permissions listed below. 12 | 13 | 0. Additional Definitions. 14 | 15 | As used herein, "this License" refers to version 3 of the GNU Lesser 16 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 17 | General Public License. 18 | 19 | "The Library" refers to a covered work governed by this License, 20 | other than an Application or a Combined Work as defined below. 21 | 22 | An "Application" is any work that makes use of an interface provided 23 | by the Library, but which is not otherwise based on the Library. 24 | Defining a subclass of a class defined by the Library is deemed a mode 25 | of using an interface provided by the Library. 26 | 27 | A "Combined Work" is a work produced by combining or linking an 28 | Application with the Library. The particular version of the Library 29 | with which the Combined Work was made is also called the "Linked 30 | Version". 31 | 32 | The "Minimal Corresponding Source" for a Combined Work means the 33 | Corresponding Source for the Combined Work, excluding any source code 34 | for portions of the Combined Work that, considered in isolation, are 35 | based on the Application, and not on the Linked Version. 36 | 37 | The "Corresponding Application Code" for a Combined Work means the 38 | object code and/or source code for the Application, including any data 39 | and utility programs needed for reproducing the Combined Work from the 40 | Application, but excluding the System Libraries of the Combined Work. 41 | 42 | 1. Exception to Section 3 of the GNU GPL. 43 | 44 | You may convey a covered work under sections 3 and 4 of this License 45 | without being bound by section 3 of the GNU GPL. 46 | 47 | 2. Conveying Modified Versions. 48 | 49 | If you modify a copy of the Library, and, in your modifications, a 50 | facility refers to a function or data to be supplied by an Application 51 | that uses the facility (other than as an argument passed when the 52 | facility is invoked), then you may convey a copy of the modified 53 | version: 54 | 55 | a) under this License, provided that you make a good faith effort to 56 | ensure that, in the event an Application does not supply the 57 | function or data, the facility still operates, and performs 58 | whatever part of its purpose remains meaningful, or 59 | 60 | b) under the GNU GPL, with none of the additional permissions of 61 | this License applicable to that copy. 62 | 63 | 3. Object Code Incorporating Material from Library Header Files. 64 | 65 | The object code form of an Application may incorporate material from 66 | a header file that is part of the Library. You may convey such object 67 | code under terms of your choice, provided that, if the incorporated 68 | material is not limited to numerical parameters, data structure 69 | layouts and accessors, or small macros, inline functions and templates 70 | (ten or fewer lines in length), you do both of the following: 71 | 72 | a) Give prominent notice with each copy of the object code that the 73 | Library is used in it and that the Library and its use are 74 | covered by this License. 75 | 76 | b) Accompany the object code with a copy of the GNU GPL and this license 77 | document. 78 | 79 | 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, 82 | taken together, effectively do not restrict modification of the 83 | portions of the Library contained in the Combined Work and reverse 84 | engineering for debugging such modifications, if you also do each of 85 | the following: 86 | 87 | a) Give prominent notice with each copy of the Combined Work that 88 | the Library is used in it and that the Library and its use are 89 | covered by this License. 90 | 91 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 92 | document. 93 | 94 | c) For a Combined Work that displays copyright notices during 95 | execution, include the copyright notice for the Library among 96 | these notices, as well as a reference directing the user to the 97 | copies of the GNU GPL and this license document. 98 | 99 | d) Do one of the following: 100 | 101 | 0) Convey the Minimal Corresponding Source under the terms of this 102 | License, and the Corresponding Application Code in a form 103 | suitable for, and under terms that permit, the user to 104 | recombine or relink the Application with a modified version of 105 | the Linked Version to produce a modified Combined Work, in the 106 | manner specified by section 6 of the GNU GPL for conveying 107 | Corresponding Source. 108 | 109 | 1) Use a suitable shared library mechanism for linking with the 110 | Library. A suitable mechanism is one that (a) uses at run time 111 | a copy of the Library already present on the user's computer 112 | system, and (b) will operate properly with a modified version 113 | of the Library that is interface-compatible with the Linked 114 | Version. 115 | 116 | e) Provide Installation Information, but only if you would otherwise 117 | be required to provide such information under section 6 of the 118 | GNU GPL, and only to the extent that such information is 119 | necessary to install and execute a modified version of the 120 | Combined Work produced by recombining or relinking the 121 | Application with a modified version of the Linked Version. (If 122 | you use option 4d0, the Installation Information must accompany 123 | the Minimal Corresponding Source and Corresponding Application 124 | Code. If you use option 4d1, you must provide the Installation 125 | Information in the manner specified by section 6 of the GNU GPL 126 | for conveying Corresponding Source.) 127 | 128 | 5. Combined Libraries. 129 | 130 | You may place library facilities that are a work based on the 131 | Library side by side in a single library together with other library 132 | facilities that are not Applications and are not covered by this 133 | License, and convey such a combined library under terms of your 134 | choice, if you do both of the following: 135 | 136 | a) Accompany the combined library with a copy of the same work based 137 | on the Library, uncombined with any other library facilities, 138 | conveyed under the terms of this License. 139 | 140 | b) Give prominent notice with the combined library that part of it 141 | is a work based on the Library, and explaining where to find the 142 | accompanying uncombined form of the same work. 143 | 144 | 6. Revised Versions of the GNU Lesser General Public License. 145 | 146 | The Free Software Foundation may publish revised and/or new versions 147 | of the GNU Lesser General Public License from time to time. Such new 148 | versions will be similar in spirit to the present version, but may 149 | differ in detail to address new problems or concerns. 150 | 151 | Each version is given a distinguishing version number. If the 152 | Library as you received it specifies that a certain numbered version 153 | of the GNU Lesser General Public License "or any later version" 154 | applies to it, you have the option of following the terms and 155 | conditions either of that published version or of any later version 156 | published by the Free Software Foundation. If the Library as you 157 | received it does not specify a version number of the GNU Lesser 158 | General Public License, you may choose any version of the GNU Lesser 159 | General Public License ever published by the Free Software Foundation. 160 | 161 | If the Library as you received it specifies that a proxy can decide 162 | whether future versions of the GNU Lesser General Public License shall 163 | apply, that proxy's public statement of acceptance of any version is 164 | permanent authorization for you to choose that version for the 165 | Library. 166 | -------------------------------------------------------------------------------- /lbg-stippling/README.md: -------------------------------------------------------------------------------- 1 | # Weighted Linde-Buzo-Gray Stippling 2 | 3 | An interactive demo application for the algorithm proposed in our SIGGRAPH Asia 2017 technical paper. 4 | Our project website (paper, video, examples) can be found [here](http://graphics.uni-konstanz.de/publikationen/Deussen2017LindeBuzoGray/index.html). 5 | 6 | ### Disclaimer 7 | This is a reimplemented demo with focus on interactivity, and not the code that was used to generate the images and timings in the paper. 8 | 9 | 10 | ### Abstract 11 | We propose an adaptive version of Lloyd's optimization method that distributes points based on Voronoi diagrams. Our inspiration is the Linde-Buzo-Gray-Algorithm in vector quantization, which dynamically splits Voronoi cells until a desired number of representative vectors is reached. We reformulate this algorithm by splitting and merging Voronoi cells based on their size, greyscale level, or variance of an underlying input image. The proposed method automatically adapts to various constraints and, in contrast to previous work, requires no good initial point distribution or prior knowledge about the final number of points. Compared to weighted Voronoi stippling the convergence rate is much higher and the spectral and spatial properties are superior. Further, because points are created based on local operations, coherent stipple animations can be produced. Our method is also able to produce good quality point sets in other fields, such as remeshing of geometry, based on local geometric features such as curvature. 12 | 13 | ### Citation 14 | ``` 15 | @article{Deussen2017LindeBuzoGray, 16 | address = {New York, NY, USA}, 17 | articleno = {233}, 18 | author = {O. Deussen and M. Spicker and Q. Zheng}, 19 | doi = {10.1145/3130800.3130819}, 20 | issue_date = {November 2017}, 21 | journal = {ACM Trans. Graph.}, 22 | keywords = {lloyd optimization, voronoi diagram, linde-buzo-gray algorithm, stippling, sampling, remeshing}, 23 | month = {nov}, 24 | number = {6}, 25 | numpages = {13}, 26 | pages = {(to appear)}, 27 | publisher = {ACM}, 28 | title = {Weighted Linde-Buzo-Gray Stippling}, 29 | url = {http://doi.acm.org/10.1145/3130800.3130819}, 30 | volume = {36}, 31 | year = {2017}, 32 | } 33 | ``` 34 | 35 | ### Dependencies 36 | The following libraries are required: 37 | * Qt5Core 38 | * Qt5Widgets 39 | * Qt5Svg 40 | 41 | ### Building 42 | ```bash 43 | mkdir build 44 | cd build 45 | cmake .. 46 | make 47 | ./LBGStippling 48 | ``` -------------------------------------------------------------------------------- /lbg-stippling/dlls/Qt5Core.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbruder/FoveatedVolumeRendering/81339beb423f0048ad680e855a25e9d486ee3cb3/lbg-stippling/dlls/Qt5Core.dll -------------------------------------------------------------------------------- /lbg-stippling/dlls/Qt5Cored.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbruder/FoveatedVolumeRendering/81339beb423f0048ad680e855a25e9d486ee3cb3/lbg-stippling/dlls/Qt5Cored.dll -------------------------------------------------------------------------------- /lbg-stippling/dlls/Qt5Gui.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbruder/FoveatedVolumeRendering/81339beb423f0048ad680e855a25e9d486ee3cb3/lbg-stippling/dlls/Qt5Gui.dll -------------------------------------------------------------------------------- /lbg-stippling/dlls/Qt5Guid.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbruder/FoveatedVolumeRendering/81339beb423f0048ad680e855a25e9d486ee3cb3/lbg-stippling/dlls/Qt5Guid.dll -------------------------------------------------------------------------------- /lbg-stippling/dlls/Qt5PrintSupport.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbruder/FoveatedVolumeRendering/81339beb423f0048ad680e855a25e9d486ee3cb3/lbg-stippling/dlls/Qt5PrintSupport.dll -------------------------------------------------------------------------------- /lbg-stippling/dlls/Qt5PrintSupportd.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbruder/FoveatedVolumeRendering/81339beb423f0048ad680e855a25e9d486ee3cb3/lbg-stippling/dlls/Qt5PrintSupportd.dll -------------------------------------------------------------------------------- /lbg-stippling/dlls/Qt5Svg.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbruder/FoveatedVolumeRendering/81339beb423f0048ad680e855a25e9d486ee3cb3/lbg-stippling/dlls/Qt5Svg.dll -------------------------------------------------------------------------------- /lbg-stippling/dlls/Qt5Svgd.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbruder/FoveatedVolumeRendering/81339beb423f0048ad680e855a25e9d486ee3cb3/lbg-stippling/dlls/Qt5Svgd.dll -------------------------------------------------------------------------------- /lbg-stippling/dlls/Qt5Widgets.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbruder/FoveatedVolumeRendering/81339beb423f0048ad680e855a25e9d486ee3cb3/lbg-stippling/dlls/Qt5Widgets.dll -------------------------------------------------------------------------------- /lbg-stippling/dlls/Qt5Widgetsd.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbruder/FoveatedVolumeRendering/81339beb423f0048ad680e855a25e9d486ee3cb3/lbg-stippling/dlls/Qt5Widgetsd.dll -------------------------------------------------------------------------------- /lbg-stippling/input/input1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbruder/FoveatedVolumeRendering/81339beb423f0048ad680e855a25e9d486ee3cb3/lbg-stippling/input/input1.jpg -------------------------------------------------------------------------------- /lbg-stippling/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * This is an interactive demo application for the algorithm proposed in: 3 | * 4 | * Weighted Linde-Buzo Gray Stippling 5 | * Oliver Deussen, Marc Spicker, Qian Zheng 6 | * 7 | * In: ACM Transactions on Graphics (Proceedings of SIGGRAPH Asia 2017) 8 | * https://doi.org/10.1145/3130800.3130819 9 | * 10 | * Copyright 2017 Marc Spicker (marc.spicker@googlemail.com) 11 | */ 12 | 13 | #include 14 | #include 15 | #include 16 | 17 | #include "lbgstippling.h" 18 | #include "mainwindow.h" 19 | 20 | QImage foveaSampling() { 21 | auto ellipticalGauss2DAppox = [](float x, float y, // 22 | float sigmaX, float sigmaY) -> float { 23 | return qExp(-((x * x) / (2.0 * sigmaX * sigmaX) + (y * y) / (2.0 * sigmaY * sigmaY))); 24 | }; 25 | 26 | const QSize screenSizePx(1024, 1024); 27 | const QSizeF screenSizeCm(30, 30); // 80, 33.5 28 | const qreal viewDistanceCm = 60; 29 | const qreal foveaAlpha = 5.0 / 180.0 * M_PI; 30 | const qreal gaussFactor = 0.7; 31 | const qreal foveaCm = viewDistanceCm * qSin(foveaAlpha); 32 | const QSizeF foveaPx(screenSizePx.width() / screenSizeCm.width() * foveaCm, 33 | screenSizePx.height() / screenSizeCm.height() * foveaCm); 34 | 35 | QImage gaussian(screenSizePx.width() * 2, screenSizePx.height() * 2, QImage::Format_Grayscale8); 36 | //QImage gaussian(screenSizePx.width() , screenSizePx.height() , QImage::Format_Grayscale8); 37 | for (int y = 0; y < gaussian.height(); ++y) 38 | { 39 | uchar* line = gaussian.scanLine(y); 40 | for (int x = 0; x < gaussian.width(); ++x) 41 | { 42 | float g = ellipticalGauss2DAppox(x - gaussian.width() / 2, y - gaussian.height() / 2, // 43 | foveaPx.width()*gaussFactor, foveaPx.height()*gaussFactor); 44 | line[x] = qMin(static_cast((1.0 - g) * 255.0), 254); 45 | } 46 | } 47 | qDebug() << "sceen size" << screenSizeCm << ", view distance" << viewDistanceCm 48 | << ", gauss factor" << gaussFactor; 49 | gaussian.save(QString::number(gaussFactor) + "_gauss.png"); 50 | return gaussian; 51 | } 52 | int main(int argc, char* argv[]) { 53 | QApplication app(argc, argv); 54 | app.setApplicationName("Weighted Linde-Buzo-Gray Stippling"); 55 | 56 | QCommandLineParser parser; 57 | parser.setApplicationDescription("This is a helping text."); 58 | parser.setSingleDashWordOptionMode(QCommandLineParser::ParseAsLongOptions); 59 | parser.addHelpOption(); 60 | 61 | const char* c = "main"; 62 | parser.addOptions({{"input", QCoreApplication::translate(c, "Input image."), 63 | QCoreApplication::translate(c, "image")}, 64 | {"ip", QCoreApplication::translate(c, "Initial number of points."), 65 | QCoreApplication::translate(c, "points")}, 66 | {"ips", QCoreApplication::translate(c, "Initial point size."), 67 | QCoreApplication::translate(c, "size")}, 68 | {"cps", QCoreApplication::translate(c, "Use constant point size.")}, 69 | {"psmin", QCoreApplication::translate(c, "Minimum point size."), 70 | QCoreApplication::translate(c, "size")}, 71 | {"psmax", QCoreApplication::translate(c, "Maximum point size."), 72 | QCoreApplication::translate(c, "size")}, 73 | {"psmapping", QCoreApplication::translate(c, "Point size mapping."), 74 | QCoreApplication::translate(c, "mapping")}, 75 | {"ss", QCoreApplication::translate(c, "Super sampling factor."), 76 | QCoreApplication::translate(c, "factor")}, 77 | {"iter", QCoreApplication::translate(c, "Maximum number iterations."), 78 | QCoreApplication::translate(c, "number")}, 79 | {"hyst", QCoreApplication::translate(c, "Algorithm hysteresis."), 80 | QCoreApplication::translate(c, "value")}, 81 | {"hystd", QCoreApplication::translate(c, "Hysteresis delta per iteration."), 82 | QCoreApplication::translate(c, "value")}, 83 | {"output", QCoreApplication::translate(c, "Output file."), 84 | QCoreApplication::translate(c, "file")}, 85 | {"batchCount", QCoreApplication::translate(c, "Total number of batches."), 86 | QCoreApplication::translate(c, "file")}, 87 | {"batchNo", QCoreApplication::translate(c, "Batch id of this run."), 88 | QCoreApplication::translate(c, "file")}, 89 | {"params", QCoreApplication::translate(c, "JSON parameter file."), 90 | QCoreApplication::translate(c, "params")}}); 91 | 92 | parser.process(app); 93 | 94 | QImage density = foveaSampling(); // QImage(":/input/input1.jpg"); 95 | LBGStippling::Params params; 96 | 97 | qDebug() << "Point size range [" << params.pointSizeMin << params.pointSizeMax << "]"; 98 | 99 | if (parser.isSet("input")) { 100 | density = QImage(parser.value("input")); 101 | } 102 | if (parser.isSet("ip")) { 103 | params.initialPoints = parser.value("ip").toInt(); 104 | } 105 | if (parser.isSet("ips")) { 106 | params.initialPointSize = parser.value("ips").toFloat(); 107 | } 108 | params.adaptivePointSize = !parser.isSet("cps"); 109 | 110 | if (parser.isSet("psmin")) { 111 | params.pointSizeMin = parser.value("psmin").toFloat(); 112 | } 113 | if (parser.isSet("psmax")) { 114 | params.pointSizeMax = parser.value("psmax").toFloat(); 115 | } 116 | if (parser.isSet("psmapping")) { 117 | int index = parser.value("psmapping").toInt(); 118 | using pmf = LBGStippling::PointMappingFunction; 119 | switch (index) { 120 | case 0: 121 | params.mapping = pmf::LINEAR; 122 | break; 123 | case 1: 124 | params.mapping = pmf::SQUAREROOT; 125 | break; 126 | case 2: 127 | params.mapping = pmf::EXPONENTIAL; 128 | break; 129 | case 3: 130 | params.mapping = pmf::SQUARE; 131 | } 132 | } 133 | if (parser.isSet("ss")) { 134 | params.superSamplingFactor = parser.value("ss").toInt(); 135 | } 136 | if (parser.isSet("iter")) { 137 | params.maxIterations = parser.value("iter").toInt(); 138 | } 139 | if (parser.isSet("hyst")) { 140 | params.hysteresis = parser.value("hyst").toFloat(); 141 | } 142 | if (parser.isSet("hystd")) { 143 | params.hysteresisDelta = parser.value("hystd").toFloat(); 144 | } 145 | if (parser.isSet("params")) { 146 | params.loadParametersJSON(parser.value("params")); 147 | } 148 | if (!parser.isSet("output")) { 149 | MainWindow* window = new MainWindow(density, params); 150 | window->show(); 151 | } else { 152 | //QString outputPath = parser.value("output"); 153 | //QStringList outputList = outputPath.split(","); 154 | // QStringList inputList = parser.value("input").split(","); 155 | 156 | // assert(inputList.size() > outputList.size()); 157 | 158 | LBGStippling stippling = LBGStippling(); 159 | 160 | // params.mapping = LBGStippling::PointMappingFunction::LINEAR; 161 | 162 | // if (outputList.size() == inputList.size()) { 163 | // // for each input one output 164 | 165 | // for (int i = 0; i < inputList.size(); ++i) { 166 | // const QString& in = inputList.at(i); 167 | // const QString& out = outputList.at(i); 168 | // //density = QImage(in); 169 | 170 | int batchCount = 1; 171 | int batchNo = 0; 172 | if (parser.isSet("batchCount")) 173 | { 174 | batchCount = parser.value("batchCount").toInt(); 175 | if (parser.isSet("batchNo")) 176 | batchNo = parser.value("batchNo").toInt(); 177 | } 178 | auto result = stippling.stipple(density, params, batchCount, batchNo); 179 | std::vector stipples = result.stipples; 180 | auto map = result.indexMap; 181 | 182 | QImage stippleMap(stipples.size(), 2, QImage::Format_RGB32); 183 | QRgb* stippleLine0 = reinterpret_cast(stippleMap.scanLine(0)); 184 | QRgb* stippleLine1 = reinterpret_cast(stippleMap.scanLine(1)); 185 | // QRgb* stippleLine2 = reinterpret_cast(stippleMap.scanLine(2)); 186 | // QRgb* stippleLine3 = reinterpret_cast(stippleMap.scanLine(3)); 187 | // QRgb* stippleLine4 = reinterpret_cast(stippleMap.scanLine(4)); 188 | // QRgb* stippleLine5 = reinterpret_cast(stippleMap.scanLine(5)); 189 | // QRgb* stippleLine6 = reinterpret_cast(stippleMap.scanLine(6)); 190 | // QRgb* stippleLine7 = reinterpret_cast(stippleMap.scanLine(7)); 191 | for (int i = 0; i < stipples.size(); ++i) { 192 | // Pos 193 | unsigned int mc = result.point2morton.value(i); 194 | // if (i < 100) qDebug() << i << mc << stipples[i].pos; 195 | stippleLine0[mc] = static_cast(stipples[i].pos.x() * density.width() ); 196 | stippleLine1[mc] = static_cast(stipples[i].pos.y() * density.height() ); 197 | // // Inner indices 198 | // stippleLine2[i] = i; 199 | // stippleLine3[i] = i; 200 | // stippleLine4[i] = i; 201 | 202 | // // Adjacent indices 203 | // stippleLine5[i] = i; 204 | // stippleLine6[i] = i; 205 | // stippleLine7[i] = i; 206 | } 207 | stippleMap.save("stippleMap.png"); 208 | 209 | QImage indexMap(map.width, map.height, QImage::Format_RGB32); 210 | for (size_t y = 0; y < map.height; ++y) { 211 | QRgb* indexMapLine = reinterpret_cast(indexMap.scanLine(y)); 212 | for (size_t x = 0; x < map.width; ++x) { 213 | uint index = map.get(x, y); 214 | indexMapLine[x] = index; 215 | } 216 | } 217 | indexMap.save("indexMap.png"); 218 | 219 | // TODO: schreib den rest 220 | 221 | QFile neighborIndexMapFile("neighborIndexMap.u32.bin"); 222 | neighborIndexMapFile.open(QIODevice::WriteOnly); 223 | neighborIndexMapFile.write( reinterpret_cast< char*>(result.neighborIndexMap.data()), result.neighborIndexMap.size() * sizeof(uint32_t)); 224 | neighborIndexMapFile.close(); 225 | 226 | QFile neighborWeightMapFile("neighborWeightMap.f32.bin"); 227 | neighborWeightMapFile.open(QIODevice::WriteOnly); 228 | neighborWeightMapFile.write( reinterpret_cast< char*>(result.neighborWeightMap.data()), result.neighborWeightMap.size() * sizeof(float)); 229 | neighborWeightMapFile.close(); 230 | 231 | 232 | 233 | // QSvgGenerator generator; 234 | // generator.setFileName(out); 235 | // generator.setSize(density.size()); 236 | // generator.setViewBox(QRectF(0, 0, density.width(), density.height())); 237 | // generator.setTitle("Stippling Result"); 238 | // generator.setDescription("SVG File created by Weighted Linde-Buzo-Gray Stippling"); 239 | 240 | // QPainter painter; 241 | // painter.begin(&generator); 242 | // painter.setPen(Qt::NoPen); 243 | 244 | // for (const auto& s : stipples) { 245 | // auto x = s.pos.x() * density.width(); 246 | // auto y = s.pos.y() * density.height(); 247 | // painter.setBrush(s.color); 248 | // painter.drawEllipse(QPointF(x, y), s.size / 2.0, s.size / 2.0); 249 | // } 250 | 251 | // painter.end(); 252 | // } 253 | // } else if (outputList.size() == 1) { 254 | // // merge all inputs into one output 255 | 256 | // QSvgGenerator generator; 257 | // generator.setFileName(outputList.at(0)); 258 | // generator.setSize(density.size()); 259 | // generator.setViewBox(QRectF(0, 0, density.width(), density.height())); 260 | // generator.setTitle("Stippling Result"); 261 | // generator.setDescription("SVG File created by Weighted Linde-Buzo-Gray Stippling"); 262 | 263 | // QPainter painter; 264 | // painter.begin(&generator); 265 | // painter.setPen(Qt::NoPen); 266 | 267 | // for (const QString& in : inputList) { 268 | // density = QImage(in); 269 | // std::vector stipples = stippling.stipple(density, params).stipples; 270 | 271 | // for (const auto& s : stipples) { 272 | // auto x = s.pos.x() * density.width(); 273 | // auto y = s.pos.y() * density.height(); 274 | // painter.setBrush(s.color); 275 | // painter.drawEllipse(QPointF(x, y), s.size / 2.0, s.size / 2.0); 276 | // } 277 | // } 278 | // painter.end(); 279 | // } 280 | exit(0); 281 | } 282 | 283 | return app.exec(); 284 | } 285 | -------------------------------------------------------------------------------- /lbg-stippling/resources.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | input/input1.jpg 4 | 5 | 6 | -------------------------------------------------------------------------------- /lbg-stippling/src/lbgstippling.cpp: -------------------------------------------------------------------------------- 1 | #include "lbgstippling.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | // And this is the "dataset to kd-tree" adaptor class: 18 | 19 | struct QVectorAdaptor { 20 | const QVector& obj; //!< A const ref to the data set origin 21 | 22 | QVectorAdaptor(const QVector& obj_) : obj(obj_) {} 23 | 24 | inline const QVector& derived() const { return obj; } 25 | 26 | inline size_t kdtree_get_point_count() const { return derived().size(); } 27 | 28 | inline float kdtree_get_pt(const size_t idx, const size_t dim) const { 29 | const auto& point = derived()[idx]; 30 | if (dim == 0) 31 | return point.x(); 32 | else 33 | return point.y(); 34 | } 35 | 36 | template 37 | bool kdtree_get_bbox(BBOX& /*bb*/) const { 38 | return false; 39 | } 40 | }; 41 | 42 | namespace Random { 43 | std::random_device rd; 44 | std::mt19937 gen(42); 45 | } // namespace Random 46 | 47 | using Params = LBGStippling::Params; 48 | using Status = LBGStippling::Status; 49 | 50 | QVector sites(const std::vector& stipples) { 51 | QVector sites(static_cast(stipples.size())); 52 | std::transform(stipples.begin(), stipples.end(), sites.begin(), 53 | [](const auto& s) { return s.pos; }); 54 | return sites; 55 | } 56 | 57 | std::vector randomStipples(size_t n, float size) { 58 | std::uniform_real_distribution dis(0.01f, 0.99f); 59 | std::vector stipples(n); 60 | std::generate(stipples.begin(), stipples.end(), [&]() { 61 | return Stipple{QVector2D(dis(Random::gen), dis(Random::gen)), size, Qt::black}; 62 | }); 63 | return stipples; 64 | } 65 | 66 | template 67 | inline T pow2(T x) { 68 | return x * x; 69 | } 70 | 71 | QVector2D jitter(QVector2D s) { 72 | using namespace Random; 73 | std::uniform_real_distribution jitter_dis(-0.001f, 0.001f); 74 | return s += QVector2D(jitter_dis(gen), jitter_dis(gen)); 75 | } 76 | 77 | float getSplitValueUpper(float pointDiameter, float hysteresis, size_t superSampling) { 78 | const float pointArea = M_PI * pow2(pointDiameter / 2.0f); 79 | return (1.0f + hysteresis / 2.0f) * pointArea * pow2(superSampling); 80 | } 81 | 82 | float getSplitValueLower(float pointDiameter, float hysteresis, size_t superSampling) { 83 | const float pointArea = M_PI * pow2(pointDiameter / 2.0f); 84 | return (1.0f - hysteresis / 2.0f) * pointArea * pow2(superSampling); 85 | } 86 | 87 | float stippleSize(const VoronoiCell& cell, const Params& params) { 88 | if (params.adaptivePointSize) { 89 | float norm; 90 | switch (params.mapping) { 91 | case LBGStippling::PointMappingFunction::LINEAR: 92 | norm = cell.sumDensity / cell.area; 93 | break; 94 | case LBGStippling::PointMappingFunction::SQUAREROOT: 95 | norm = std::sqrt(cell.sumDensity / cell.area); 96 | break; 97 | case LBGStippling::PointMappingFunction::EXPONENTIAL: 98 | norm = 2.0f - 2.0f / (1.0f + cell.sumDensity / cell.area); 99 | break; 100 | case LBGStippling::PointMappingFunction::SQUARE: 101 | norm = pow2(cell.sumDensity / cell.area); 102 | }; 103 | return params.pointSizeMin * (1.0f - norm) + params.pointSizeMax * norm; 104 | } else { 105 | return params.initialPointSize; 106 | } 107 | } 108 | 109 | float currentHysteresis(size_t i, const Params& params) { 110 | return params.hysteresis + i * params.hysteresisDelta; 111 | } 112 | 113 | bool notFinished(const Status& status, const Params& params) { 114 | auto [iteration, size, splits, merges, hysteresis] = status; 115 | return !((splits == 0 && merges == 0) || (iteration == params.maxIterations)); 116 | } 117 | 118 | LBGStippling::LBGStippling() { 119 | m_statusCallback = [](const Status&) {}; 120 | m_stippleCallback = [](const std::vector&) {}; 121 | m_cellCallback = [](const IndexMap&) {}; 122 | } 123 | 124 | void LBGStippling::setStatusCallback(Report statusCB) { 125 | m_statusCallback = statusCB; 126 | } 127 | 128 | void LBGStippling::setStippleCallback(Report> stippleCB) { 129 | m_stippleCallback = stippleCB; 130 | } 131 | 132 | void LBGStippling::setCellCallback(Report cellCB) { 133 | m_cellCallback = cellCB; 134 | } 135 | 136 | /** 137 | * @brief encodeMorton 138 | * @see http://www.graphics.stanford.edu/~seander/bithacks.html#InterleaveBMN 139 | * @param p point 140 | * @param width Index map width 141 | * @param height Index map height 142 | * @return morton encoded index 143 | */ 144 | unsigned int encodeMorton(QVector2D p, size_t width, size_t height) 145 | { 146 | static const unsigned int B[] = {0x55555555, 0x33333333, 0x0F0F0F0F, 0x00FF00FF}; 147 | static const unsigned int S[] = {1, 2, 4, 8}; 148 | 149 | // x and y must initially be less than 65536. 150 | unsigned int x = static_cast(p.x() * width); // Interleave lower 16 bits of x and y, so the bits of x 151 | unsigned int y = static_cast(p.y() * height); // are in the even positions and bits from y in the odd; 152 | 153 | x = (x | (x << S[3])) & B[3]; 154 | x = (x | (x << S[2])) & B[2]; 155 | x = (x | (x << S[1])) & B[1]; 156 | x = (x | (x << S[0])) & B[0]; 157 | 158 | y = (y | (y << S[3])) & B[3]; 159 | y = (y | (y << S[2])) & B[2]; 160 | y = (y | (y << S[1])) & B[1]; 161 | y = (y | (y << S[0])) & B[0]; 162 | 163 | // return the resulting 32-bit Morton Number. 164 | return x | (y << 1); 165 | } 166 | 167 | LBGStippling::Result LBGStippling::stipple(const QImage& density, const Params& params, 168 | const int batchCount, const int batchNo) const { 169 | QImage densityGray = 170 | density 171 | .scaledToWidth(static_cast(params.superSamplingFactor) * density.width(), 172 | Qt::SmoothTransformation) 173 | .convertToFormat(QImage::Format_Grayscale8); 174 | 175 | VoronoiDiagram voronoi(densityGray); 176 | 177 | std::vector stipples = randomStipples(params.initialPoints, params.initialPointSize); 178 | 179 | IndexMap indexMap; 180 | 181 | Status status = {0, 0, 1, 1, 0.5f}; 182 | 183 | QVector points; 184 | 185 | qDebug() << "LBG: Starting..."; 186 | 187 | while (notFinished(status, params)) { 188 | status.splits = 0; 189 | status.merges = 0; 190 | 191 | points = sites(stipples); 192 | if (points.empty()) { 193 | // Stop if image is empty (all white). 194 | m_stippleCallback(stipples); 195 | break; 196 | } 197 | 198 | indexMap = voronoi.calculate(points); 199 | std::vector cells = accumulateCells(indexMap, densityGray); 200 | 201 | assert(cells.size() == stipples.size()); 202 | 203 | stipples.clear(); 204 | 205 | float hysteresis = currentHysteresis(status.iteration, params); 206 | status.hysteresis = hysteresis; 207 | 208 | for (const auto& cell : cells) { 209 | float totalDensity = cell.sumDensity; 210 | float diameter = stippleSize(cell, params); 211 | 212 | if (totalDensity < 213 | getSplitValueLower(diameter, hysteresis, params.superSamplingFactor) || 214 | cell.area == 0.0f) { 215 | // cell too small - merge 216 | ++status.merges; 217 | continue; 218 | } 219 | 220 | if (totalDensity < 221 | getSplitValueUpper(diameter, hysteresis, params.superSamplingFactor)) { 222 | // cell size within acceptable range - keep 223 | stipples.push_back({cell.centroid, diameter, Qt::black}); 224 | continue; 225 | } 226 | 227 | // cell too large - split 228 | float area = std::max(1.0f, cell.area); 229 | float circleRadius = std::sqrt(area / M_PI); 230 | QVector2D splitVector = QVector2D(0.5f * circleRadius, 0.0f); 231 | 232 | float a = cell.orientation; 233 | QVector2D splitVectorRotated = 234 | QVector2D(splitVector.x() * std::cos(a) - splitVector.y() * std::sin(a), 235 | splitVector.y() * std::cos(a) + splitVector.x() * std::sin(a)); 236 | 237 | splitVectorRotated.setX(splitVectorRotated.x() / densityGray.width()); 238 | splitVectorRotated.setY(splitVectorRotated.y() / densityGray.height()); 239 | 240 | QVector2D splitSeed1 = cell.centroid - splitVectorRotated; 241 | QVector2D splitSeed2 = cell.centroid + splitVectorRotated; 242 | 243 | // check boundaries 244 | splitSeed1.setX(std::max(0.0f, std::min(splitSeed1.x(), 1.0f))); 245 | splitSeed1.setY(std::max(0.0f, std::min(splitSeed1.y(), 1.0f))); 246 | 247 | splitSeed2.setX(std::max(0.0f, std::min(splitSeed2.x(), 1.0f))); 248 | splitSeed2.setY(std::max(0.0f, std::min(splitSeed2.y(), 1.0f))); 249 | 250 | stipples.push_back({jitter(splitSeed1), diameter, Qt::red}); 251 | stipples.push_back({jitter(splitSeed2), diameter, Qt::red}); 252 | 253 | ++status.splits; 254 | } 255 | status.size = stipples.size(); 256 | m_statusCallback(status); 257 | m_cellCallback(indexMap); 258 | m_stippleCallback(stipples); 259 | 260 | ++status.iteration; 261 | } 262 | qDebug() << "LBG: Done"; 263 | qDebug() << "Number of points: " << points.size(); 264 | 265 | // stipples+points -> z ordering map 266 | QMap mortonMap; 267 | for (unsigned int i = 0; i < points.size(); ++i) 268 | { 269 | unsigned int mc = encodeMorton(points.at(i), indexMap.width, indexMap.height); 270 | mortonMap.insert(mc, i); 271 | } 272 | 273 | // debug: draw morton ordering 274 | QImage dbgImg = QPixmap(2048,2048).toImage(); 275 | dbgImg.fill(Qt::white); 276 | QPainter dbgPainter(&dbgImg); 277 | QVector dbgPoints; 278 | for (auto &a: mortonMap.values()) 279 | { 280 | dbgPoints.push_back(points.at(a).toPointF()*2048.); 281 | } 282 | dbgPainter.drawPolyline(dbgPoints); 283 | dbgPainter.end(); 284 | dbgImg.save(QString::number(points.size()) + "morton.png"); 285 | 286 | // map: point id -> morton order id (compacted) 287 | QList point2morton = mortonMap.values(); 288 | 289 | const size_t batchSize = indexMap.height / batchCount; 290 | const size_t BucketCount = 16; 291 | std::vector neighborIndexMap; 292 | std::vector neighborWeightMap; 293 | 294 | neighborIndexMap.resize(indexMap.width * batchSize * BucketCount, 0); 295 | neighborWeightMap.resize(indexMap.width * batchSize * BucketCount, 0.0f); 296 | 297 | #if true 298 | qDebug() << "Starting batch" << batchNo << "/" << batchCount << "with size" << batchSize 299 | << indexMap.width << "x" << indexMap.height; 300 | 301 | using namespace nanoflann; 302 | typedef KDTreeSingleIndexAdaptor, QVectorAdaptor, 2> 303 | my_kd_tree_t; 304 | 305 | QVectorAdaptor adaptor(points); 306 | my_kd_tree_t index(2, adaptor, KDTreeSingleIndexAdaptorParams(10)); 307 | index.buildIndex(); 308 | 309 | QElapsedTimer progressTimer; 310 | progressTimer.start(); 311 | 312 | // QElapsedTimer perfTimer; 313 | // perfTimer.start(); 314 | 315 | const size_t k = BucketCount; // + 4; 316 | std::vector ret_indices(k); 317 | std::vector out_dists_sqr(k); 318 | 319 | for (int y = batchNo*batchSize; y < batchNo*batchSize + batchSize; ++y) 320 | { 321 | int yOffset = (y - batchNo * batchSize); 322 | for (int x = 0; x < indexMap.width; ++x) 323 | { 324 | if (x == 1 && (y % 10 == 0)) 325 | { 326 | float pointsTotal = static_cast(indexMap.width * batchSize); 327 | float pointsProgress = static_cast(yOffset * indexMap.width + x) / pointsTotal; 328 | auto elapsedTime = progressTimer.elapsed(); 329 | auto remainingTime = ((1. - pointsProgress) / pointsProgress) * elapsedTime; 330 | qDebug() << "Natural Neighbor" << qSetRealNumberPrecision(5) << pointsProgress 331 | << elapsedTime / 1000. << "sec" << remainingTime / 1000. / 60. << "min"; 332 | } 333 | 334 | // Insert point at pixel to compute the modified voronoi diagram. 335 | QVector2D modifierPoint(static_cast(x) / indexMap.width, 336 | static_cast(y) / indexMap.height); 337 | QVector pointsModified; 338 | 339 | float query_pt[2] = {modifierPoint.x(), modifierPoint.y()}; 340 | nanoflann::KNNResultSet resultSet(k); 341 | resultSet.init(&ret_indices[0], &out_dists_sqr[0]); 342 | bool yay = index.findNeighbors(resultSet, &query_pt[0], nanoflann::SearchParams()); 343 | assert(yay && "Ohne KNN gehts halt net"); 344 | for (size_t i = 0; i < k; ++i) { pointsModified.append(points[ret_indices[i]]); } 345 | 346 | pointsModified.append(modifierPoint); 347 | IndexMap indexMapModified = voronoi.calculate(pointsModified); 348 | 349 | // Count intersection for each cell in the original index map. 350 | float intersectionSum = 0; 351 | QMap intersectionSet; 352 | uint32_t modifierPointIndex = indexMapModified.get(x, y); 353 | 354 | int kernelHeight = 32; //indexMapModified.height /4; 355 | int kernelWidth = 32; //indexMapModified.width /4; 356 | bool overflow = false; 357 | //#pragma omp parallel for 358 | for (int yy = std::max(yOffset - kernelHeight, 0); yy < std::min(y + kernelHeight, static_cast(indexMapModified.height)); ++yy) 359 | { 360 | for (int xx = std::max(x - kernelWidth, 0); xx < std::min(x + kernelWidth, static_cast(indexMapModified.width)); ++xx) 361 | { 362 | if (indexMapModified.get(xx, yy) == modifierPointIndex) 363 | { 364 | if (intersectionSet.size() >= BucketCount) 365 | { 366 | // qDebug() << "__Bucket overflow on pixel" << x << y; 367 | overflow = true; 368 | } 369 | else 370 | { 371 | intersectionSum++; 372 | } 373 | // encode in morton order 374 | auto originalIndex = point2morton.value(indexMap.get(xx, yy)); 375 | intersectionSet[originalIndex]++; 376 | } 377 | } 378 | } 379 | // qDebug() << "# pixels" << intersectionSum << "time " << perfTimer.restart(); 380 | // assert(intersectionSet.size() < BucketCount && "Mehr geht halt net erstmal..."); 381 | 382 | auto smap = intersectionSet.toStdMap(); 383 | while (smap.size() > BucketCount) 384 | { 385 | qDebug() << "__Bucket overflow on pixel" << x << y << "size" << smap.size(); 386 | auto it = min_element(smap.begin(), smap.end(), 387 | [](decltype(smap)::value_type & l, decltype(smap)::value_type& r) -> bool { return l.second < r.second; }); 388 | smap.erase(it); 389 | } 390 | intersectionSet = QMap(smap); 391 | 392 | // Normalize weights and copy to neighbor maps. 393 | size_t bucketIndex = 0; 394 | for (auto key : intersectionSet.keys()) 395 | { 396 | intersectionSet[key] = intersectionSet[key] / intersectionSum; 397 | auto offset = (yOffset * indexMap.width + x) * BucketCount + bucketIndex; 398 | neighborIndexMap[offset] = key; 399 | neighborWeightMap[offset] = intersectionSet[key]; 400 | bucketIndex++; 401 | } 402 | } 403 | } 404 | 405 | qDebug() << "Natural Neighbor: Done"; 406 | qDebug() << "Batch no" << batchNo << " / " << batchCount; 407 | qDebug() << "Number of points: " << points.size() << params.pointSizeMin << params.pointSizeMax; 408 | #endif 409 | return {stipples, indexMap, neighborWeightMap, neighborIndexMap, point2morton}; 410 | } 411 | 412 | void LBGStippling::Params::saveParametersJSON(const QString& path) { 413 | QJsonObject json; 414 | json.insert("initialPoints", QJsonValue(initialPoints)); 415 | json.insert("initialPointSize", QJsonValue(initialPointSize)); 416 | json.insert("adaptivePointSize", QJsonValue(adaptivePointSize)); 417 | json.insert("pointSizeMin", QJsonValue(pointSizeMin)); 418 | json.insert("pointSizeMax", QJsonValue(pointSizeMax)); 419 | QString sMapping; 420 | using pmf = LBGStippling::PointMappingFunction; 421 | switch (mapping) { 422 | case pmf::LINEAR: 423 | sMapping = "Linear"; 424 | break; 425 | case pmf::SQUAREROOT: 426 | sMapping = "SquareRoot"; 427 | break; 428 | case pmf::EXPONENTIAL: 429 | sMapping = "Exponential"; 430 | break; 431 | case pmf::SQUARE: 432 | sMapping = "Square"; 433 | }; 434 | json.insert("pointSizeMapping", QJsonValue(sMapping)); 435 | json.insert("superSamplingFactor", QJsonValue((int)superSamplingFactor)); 436 | json.insert("maxIterations", QJsonValue((int)maxIterations)); 437 | json.insert("hysteresis", QJsonValue(hysteresis)); 438 | json.insert("hysteresisDelta", QJsonValue(hysteresisDelta)); 439 | 440 | QJsonDocument jsonDoc(json); 441 | QFile jsonFile(path + ".json"); 442 | jsonFile.open(QFile::WriteOnly); 443 | jsonFile.write(jsonDoc.toJson()); 444 | jsonFile.close(); 445 | } 446 | 447 | void LBGStippling::Params::loadParametersJSON(const QString& path) { 448 | QFile jsonFile(path); 449 | jsonFile.open(QFile::ReadOnly); 450 | QJsonDocument jsonDoc = QJsonDocument::fromJson(jsonFile.readAll()); 451 | jsonFile.close(); 452 | QJsonObject json = jsonDoc.object(); 453 | 454 | initialPoints = json["initialPoints"].toInt(); 455 | initialPointSize = json["initialPointSize"].toDouble(); 456 | adaptivePointSize = json["adaptivePointSize"].toBool(); 457 | pointSizeMin = json["pointSizeMin"].toDouble(); 458 | pointSizeMax = json["pointSizeMax"].toDouble(); 459 | QString sMapping = json["pointSizeMapping"].toString(); 460 | using pmf = LBGStippling::PointMappingFunction; 461 | if (sMapping == "Linear") 462 | mapping = pmf::LINEAR; 463 | else if (sMapping == "SquareRoot") 464 | mapping = pmf::SQUAREROOT; 465 | else if (sMapping == "Exponential") 466 | mapping = pmf::EXPONENTIAL; 467 | else if (sMapping == "Square") 468 | mapping = pmf::SQUARE; 469 | superSamplingFactor = json["superSamplingFactor"].toInt(); 470 | maxIterations = json["maxIterations"].toInt(); 471 | hysteresis = json["hysteresis"].toDouble(); 472 | hysteresisDelta = json["hysteresisDelta"].toDouble(); 473 | } 474 | -------------------------------------------------------------------------------- /lbg-stippling/src/lbgstippling.h: -------------------------------------------------------------------------------- 1 | #ifndef LBGSTIPPLING_H 2 | #define LBGSTIPPLING_H 3 | 4 | #include "voronoidiagram.h" 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "voronoicell.h" 14 | 15 | // TODO: Color is only used for debugging 16 | struct Stipple { 17 | QVector2D pos; 18 | float size; 19 | QColor color; 20 | }; 21 | 22 | class LBGStippling { 23 | 24 | public: 25 | enum PointMappingFunction { LINEAR = 0, SQUAREROOT = 1, EXPONENTIAL = 2, SQUARE = 3 }; 26 | 27 | struct Params { 28 | int initialPoints = 10000; 29 | double initialPointSize = 1.0; 30 | 31 | bool adaptivePointSize = true; 32 | double pointSizeMin = 0.5; 33 | double pointSizeMax = 2.0; 34 | 35 | PointMappingFunction mapping = PointMappingFunction::SQUAREROOT; 36 | 37 | size_t superSamplingFactor = 1; 38 | size_t maxIterations = 64; 39 | 40 | double hysteresis = 0.5; 41 | double hysteresisDelta = 0.01; 42 | 43 | void saveParametersJSON(const QString& path); 44 | void loadParametersJSON(const QString& path); 45 | }; 46 | 47 | struct Status { 48 | size_t iteration; 49 | size_t size; 50 | size_t splits; 51 | size_t merges; 52 | float hysteresis; 53 | }; 54 | 55 | struct Result { 56 | std::vector stipples; 57 | IndexMap indexMap; 58 | std::vector neighborWeightMap; 59 | std::vector neighborIndexMap ; 60 | QList point2morton; 61 | }; 62 | 63 | template 64 | using Report = std::function; 65 | 66 | LBGStippling(); 67 | 68 | Result stipple(const QImage& density, const Params& params, const int batchCount = 1, 69 | const int batchNo = 0) const; 70 | 71 | // TODO: Rename and method chaining. 72 | void setStatusCallback(Report statusCB); 73 | void setStippleCallback(Report> stippleCB); 74 | void setCellCallback(Report cellCB); 75 | 76 | private: 77 | Report m_statusCallback; 78 | Report> m_stippleCallback; 79 | Report m_cellCallback; 80 | }; 81 | 82 | #endif // LBGSTIPPLING_H 83 | -------------------------------------------------------------------------------- /lbg-stippling/src/mainwindow.cpp: -------------------------------------------------------------------------------- 1 | #include "mainwindow.h" 2 | #include "settingswidget.h" 3 | #include "stippleviewer.h" 4 | 5 | MainWindow::MainWindow(QImage& density, LBGStippling::Params& params) { 6 | layout()->setSizeConstraint(QLayout::SetFixedSize); 7 | 8 | m_stippleViewer = new StippleViewer(density, this); 9 | m_stippleViewer->setAlignment(Qt::AlignLeft | Qt::AlignTop); 10 | 11 | setCentralWidget(m_stippleViewer); 12 | 13 | QDockWidget* dockWidget = new QDockWidget("Algorithm Parameters", this); 14 | dockWidget->setFeatures(QDockWidget::DockWidgetFloatable | QDockWidget::DockWidgetMovable); 15 | dockWidget->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); 16 | addDockWidget(Qt::LeftDockWidgetArea, dockWidget); 17 | 18 | SettingsWidget* settingsWidget = new SettingsWidget(m_stippleViewer, dockWidget, params); 19 | dockWidget->setWidget(settingsWidget); 20 | connect(dockWidget, &QDockWidget::topLevelChanged, [this](bool) { resize(sizeHint()); }); 21 | 22 | m_statusBar = new QStatusBar(this); 23 | m_statusBar->setSizeGripEnabled(false); 24 | setStatusBar(m_statusBar); 25 | 26 | connect(m_stippleViewer, &StippleViewer::iterationStatus, 27 | [this](int iteration, int numberPoints, int splits, int merges, float hysteresis) { 28 | m_statusBar->showMessage( 29 | "Iteration: " + QString::number(iteration) + 30 | " | Number points: " + QString::number(numberPoints) + 31 | " | Current hysteresis: " + QString::number(hysteresis, 'f', 2) + 32 | " | Splits: " + QString::number(splits) + 33 | " | Merges: " + QString::number(merges)); 34 | }); 35 | connect(m_stippleViewer, &StippleViewer::inputImageChanged, 36 | [this]() { m_statusBar->clearMessage(); }); 37 | } 38 | -------------------------------------------------------------------------------- /lbg-stippling/src/mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include "lbgstippling.h" 5 | 6 | #include 7 | 8 | class StippleViewer; 9 | 10 | class MainWindow : public QMainWindow { 11 | 12 | public: 13 | MainWindow(QImage& density, LBGStippling::Params& params); 14 | 15 | private: 16 | StippleViewer* m_stippleViewer; 17 | QStatusBar* m_statusBar; 18 | }; 19 | 20 | #endif // MAINWINDOW_H 21 | -------------------------------------------------------------------------------- /lbg-stippling/src/settingswidget.cpp: -------------------------------------------------------------------------------- 1 | #include "settingswidget.h" 2 | #include "stippleviewer.h" 3 | 4 | Q_DECLARE_METATYPE(LBGStippling::PointMappingFunction) 5 | 6 | SettingsWidget::SettingsWidget(StippleViewer* stippleViewer, 7 | QWidget* parent, 8 | LBGStippling::Params& params) 9 | : QWidget(parent), m_stippleViewer(stippleViewer), m_params(params) { 10 | 11 | QVBoxLayout* layout = new QVBoxLayout(this); 12 | setLayout(layout); 13 | setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); 14 | 15 | // point related stuff 16 | QGroupBox* pointGroup = new QGroupBox("Point Settings:", this); 17 | 18 | QLabel* initialPointLabel = new QLabel("Initial points:", this); 19 | QSpinBox* spinInitialPoints = new QSpinBox(this); 20 | spinInitialPoints->setRange(1, 100000); 21 | spinInitialPoints->setValue(m_params.initialPoints); 22 | spinInitialPoints->setToolTip( 23 | "The number of points the algorithm starts with, must be at least one."); 24 | connect(spinInitialPoints, QOverload::of(&QSpinBox::valueChanged), 25 | [this](int value) { m_params.initialPoints = value; }); 26 | 27 | QLabel* initialPointSizeLabel = new QLabel("Point Size:", this); 28 | QDoubleSpinBox* spinInitialPointSize = new QDoubleSpinBox(this); 29 | spinInitialPointSize->setRange(0.1, 50.0); 30 | spinInitialPointSize->setValue(m_params.initialPointSize); 31 | spinInitialPointSize->setSingleStep(0.1); 32 | spinInitialPointSize->setToolTip("The point size used by the algorithm. This " 33 | "will only be used when adaptive point size " 34 | "is off."); 35 | connect(spinInitialPointSize, QOverload::of(&QDoubleSpinBox::valueChanged), 36 | [this](double value) { m_params.initialPointSize = value; }); 37 | 38 | QCheckBox* adaptivePointSize = new QCheckBox("Use adaptive point size.", this); 39 | adaptivePointSize->setChecked(m_params.adaptivePointSize); 40 | adaptivePointSize->setToolTip("If enabled the algorithm will automatically " 41 | "determine the point size in the given minimum " 42 | "and maximum range."); 43 | connect(adaptivePointSize, &QCheckBox::clicked, 44 | [this](bool value) { m_params.adaptivePointSize = value; }); 45 | 46 | QLabel* minPointSize = new QLabel("Minimal Point Size:", this); 47 | QDoubleSpinBox* spinMinPointSize = new QDoubleSpinBox(this); 48 | spinMinPointSize->setRange(0.1, 50.0); 49 | spinMinPointSize->setValue(m_params.pointSizeMin); 50 | spinMinPointSize->setSingleStep(0.1); 51 | spinMinPointSize->setToolTip( 52 | "The minimum point size used when adaptive point size is enabled."); 53 | connect(spinMinPointSize, QOverload::of(&QDoubleSpinBox::valueChanged), 54 | [this](double value) { m_params.pointSizeMin = value; }); 55 | 56 | QLabel* maxPointSize = new QLabel("Maximal Point Size:", this); 57 | QDoubleSpinBox* spinMaxPointSize = new QDoubleSpinBox(this); 58 | spinMaxPointSize->setRange(0.1, 50.0); 59 | spinMaxPointSize->setValue(m_params.pointSizeMax); 60 | spinMaxPointSize->setSingleStep(0.1); 61 | spinMaxPointSize->setToolTip( 62 | "The maximum point size used when adaptive point size is enabled."); 63 | connect(spinMaxPointSize, QOverload::of(&QDoubleSpinBox::valueChanged), 64 | [this](double value) { m_params.pointSizeMax = value; }); 65 | 66 | QLabel* pointSizeScalingsLabel = new QLabel("Point Size Mapping:", this); 67 | QComboBox* pointSizeScalings = new QComboBox(this); 68 | { 69 | using pmf = LBGStippling::PointMappingFunction; 70 | pointSizeScalings->addItem("Linear", QVariant::fromValue(pmf::LINEAR)); 71 | pointSizeScalings->addItem("Square root", QVariant::fromValue(pmf::SQUAREROOT)); 72 | pointSizeScalings->addItem("Exponential", QVariant::fromValue(pmf::EXPONENTIAL)); 73 | pointSizeScalings->addItem("Square", QVariant::fromValue(pmf::SQUARE)); 74 | pointSizeScalings->setCurrentIndex(params.mapping); 75 | pointSizeScalings->setToolTip("How the minimal and maximal point size is mapped."); 76 | connect(pointSizeScalings, QOverload::of(&QComboBox::currentIndexChanged), 77 | [this, pointSizeScalings](int index) { 78 | m_params.mapping = pointSizeScalings->itemData(index).value(); 79 | }); 80 | } 81 | 82 | QGridLayout* pointGroupLayout = new QGridLayout(pointGroup); 83 | pointGroup->setLayout(pointGroupLayout); 84 | pointGroupLayout->addWidget(initialPointLabel, 0, 0); 85 | pointGroupLayout->addWidget(spinInitialPoints, 0, 1); 86 | pointGroupLayout->addWidget(initialPointSizeLabel, 1, 0); 87 | pointGroupLayout->addWidget(spinInitialPointSize, 1, 1); 88 | pointGroupLayout->addWidget(adaptivePointSize, 2, 0); 89 | pointGroupLayout->addWidget(minPointSize, 3, 0); 90 | pointGroupLayout->addWidget(spinMinPointSize, 3, 1); 91 | pointGroupLayout->addWidget(maxPointSize, 4, 0); 92 | pointGroupLayout->addWidget(spinMaxPointSize, 4, 1); 93 | pointGroupLayout->addWidget(pointSizeScalingsLabel, 5, 0); 94 | pointGroupLayout->addWidget(pointSizeScalings, 5, 1); 95 | 96 | layout->addWidget(pointGroup); 97 | 98 | // algo related stuff 99 | QGroupBox* algoGroup = new QGroupBox("Algorithm Settings:", this); 100 | 101 | QLabel* hysteresisLabel = new QLabel("Hysteresis:", this); 102 | QDoubleSpinBox* spinHysterresis = new QDoubleSpinBox(this); 103 | spinHysterresis->setRange(0.1, 3.0); 104 | spinHysterresis->setValue(m_params.hysteresis); 105 | spinHysterresis->setSingleStep(0.1); 106 | spinHysterresis->setToolTip("How close to 'perfect' the cell size has to be " 107 | "in order not to be split:\n Lower values mean " 108 | "slower convergence but higher quality results " 109 | "and vice versa."); 110 | connect(spinHysterresis, QOverload::of(&QDoubleSpinBox::valueChanged), 111 | [this](double value) { m_params.hysteresis = value; }); 112 | 113 | QLabel* hysteresisDeltaLabel = new QLabel("Hysteresis increment:", this); 114 | QDoubleSpinBox* spinHysteresisDelta = new QDoubleSpinBox(this); 115 | spinHysteresisDelta->setRange(0.0, 0.1); 116 | spinHysteresisDelta->setValue(m_params.hysteresisDelta); 117 | spinHysteresisDelta->setSingleStep(0.001); 118 | spinHysteresisDelta->setDecimals(3); 119 | spinHysteresisDelta->setToolTip("The increment of the hysteresis in each iteration."); 120 | connect(spinHysteresisDelta, QOverload::of(&QDoubleSpinBox::valueChanged), 121 | [this](double value) { m_params.hysteresisDelta = value; }); 122 | 123 | QLabel* maxIterLabel = new QLabel("Maximum Iterations:", this); 124 | QSpinBox* spinMaxIter = new QSpinBox(this); 125 | spinMaxIter->setRange(1, 1000); 126 | spinMaxIter->setValue(m_params.maxIterations); 127 | spinMaxIter->setToolTip("The maximum number of iterations until the " 128 | "algorithm stops (may stop before if no further " 129 | "changes)."); 130 | connect(spinMaxIter, QOverload::of(&QSpinBox::valueChanged), 131 | [this](int value) { m_params.maxIterations = value; }); 132 | 133 | QLabel* superSampleLabel = new QLabel("Super-Sampling Factor:", this); 134 | QSpinBox* spinSuperSample = new QSpinBox(this); 135 | spinSuperSample->setRange(1, 3); 136 | spinSuperSample->setValue(m_params.superSamplingFactor); 137 | spinSuperSample->setToolTip("Increases the size and percision of the Voronoi " 138 | "diagram, but makes the calculation slower."); 139 | connect(spinSuperSample, QOverload::of(&QSpinBox::valueChanged), 140 | [this](int value) { m_params.superSamplingFactor = value; }); 141 | 142 | QGridLayout* algoGroupLayout = new QGridLayout(algoGroup); 143 | algoGroup->setLayout(algoGroupLayout); 144 | algoGroupLayout->addWidget(hysteresisLabel, 0, 0); 145 | algoGroupLayout->addWidget(spinHysterresis, 0, 1); 146 | algoGroupLayout->addWidget(hysteresisDeltaLabel, 1, 0); 147 | algoGroupLayout->addWidget(spinHysteresisDelta, 1, 1); 148 | algoGroupLayout->addWidget(maxIterLabel, 2, 0); 149 | algoGroupLayout->addWidget(spinMaxIter, 2, 1); 150 | algoGroupLayout->addWidget(superSampleLabel, 3, 0); 151 | algoGroupLayout->addWidget(spinSuperSample, 3, 1); 152 | 153 | layout->addWidget(algoGroup); 154 | 155 | QGroupBox* jsonBox = new QGroupBox("Load params from JSON:", this); 156 | QVBoxLayout* jsonBoxLayout = new QVBoxLayout(jsonBox); 157 | jsonBox->setLayout(jsonBoxLayout); 158 | QPushButton* jsonButton = new QPushButton("Select", this); 159 | jsonBoxLayout->addWidget(jsonButton); 160 | connect(jsonButton, &QPushButton::pressed, [=]() { 161 | QString path = QFileDialog::getOpenFileName(this, tr("Open Settings File"), QString(), 162 | tr("Settings (*.json)")); 163 | 164 | if (path.isEmpty()) 165 | return; 166 | 167 | m_params.loadParametersJSON(path); 168 | 169 | // update GUI 170 | spinInitialPoints->setValue(m_params.initialPoints); 171 | spinInitialPointSize->setValue(m_params.initialPointSize); 172 | adaptivePointSize->setChecked(m_params.adaptivePointSize); 173 | spinMinPointSize->setValue(m_params.pointSizeMin); 174 | spinMaxPointSize->setValue(m_params.pointSizeMax); 175 | spinSuperSample->setValue(m_params.superSamplingFactor); 176 | spinMaxIter->setValue(m_params.maxIterations); 177 | spinHysterresis->setValue(m_params.hysteresis); 178 | spinHysteresisDelta->setValue(m_params.hysteresisDelta); 179 | pointSizeScalings->setCurrentIndex(m_params.mapping); 180 | }); 181 | 182 | layout->addWidget(jsonBox); 183 | 184 | // open button 185 | QGroupBox* openBox = new QGroupBox("Open file as input:", this); 186 | QVBoxLayout* openBoxLayout = new QVBoxLayout(openBox); 187 | openBox->setLayout(openBoxLayout); 188 | QPushButton* fileButton = new QPushButton("Select", this); 189 | openBoxLayout->addWidget(fileButton); 190 | connect(fileButton, &QPushButton::pressed, [this]() { 191 | QString path = QFileDialog::getOpenFileName(this, tr("Open Image"), QString(), 192 | tr("Image Files (*.png *.jpg *.bmp *.jpeg)")); 193 | 194 | if (path.isEmpty()) 195 | return; 196 | 197 | disableSaveButtons(); 198 | QImage img(path); 199 | m_stippleViewer->setInputImage(img); 200 | }); 201 | 202 | layout->addWidget(openBox); 203 | 204 | // save buttons 205 | QGroupBox* saveGroup = new QGroupBox("Save as:", this); 206 | QHBoxLayout* saveLayout = new QHBoxLayout(saveGroup); 207 | m_savePNG = new QPushButton("PNG", this); 208 | m_savePNG->setEnabled(false); 209 | m_saveSVG = new QPushButton("SVG", this); 210 | m_saveSVG->setEnabled(false); 211 | m_savePDF = new QPushButton("PDF", this); 212 | m_savePDF->setEnabled(false); 213 | 214 | saveLayout->addWidget(m_savePNG); 215 | saveLayout->addWidget(m_saveSVG); 216 | saveLayout->addWidget(m_savePDF); 217 | saveGroup->setLayout(saveLayout); 218 | 219 | connect(m_saveSVG, &QPushButton::pressed, [this]() { 220 | QFileDialog dialog(this, tr("Save Image as SVG"), QString(), tr("SVG Image (*.svg)")); 221 | dialog.setAcceptMode(QFileDialog::AcceptSave); 222 | dialog.setDefaultSuffix("svg"); 223 | if (dialog.exec() == 0) 224 | return; 225 | 226 | QString path = dialog.selectedFiles().first(); 227 | 228 | if (path.isEmpty()) 229 | return; 230 | 231 | m_stippleViewer->saveImageSVG(path); 232 | m_params.saveParametersJSON(path); 233 | }); 234 | 235 | connect(m_savePDF, &QPushButton::pressed, [this]() { 236 | QFileDialog dialog(this, tr("Save Image as PDF"), QString(), tr("PDF (*.pdf)")); 237 | dialog.setAcceptMode(QFileDialog::AcceptSave); 238 | dialog.setDefaultSuffix("pdf"); 239 | if (dialog.exec() == 0) 240 | return; 241 | 242 | QString path = dialog.selectedFiles().first(); 243 | 244 | if (path.isEmpty()) 245 | return; 246 | 247 | m_stippleViewer->saveImagePDF(path); 248 | m_params.saveParametersJSON(path); 249 | }); 250 | 251 | connect(m_stippleViewer, &StippleViewer::finished, this, &SettingsWidget::enableSaveButtons); 252 | 253 | connect(m_savePNG, &QPushButton::pressed, [this]() { 254 | QFileDialog dialog(this, tr("Save Image as PNG"), QString(), tr("PNG Image (*.png)")); 255 | dialog.setAcceptMode(QFileDialog::AcceptSave); 256 | dialog.setDefaultSuffix("png"); 257 | 258 | if (dialog.exec() == 0) 259 | return; 260 | 261 | QString path = dialog.selectedFiles().first(); 262 | 263 | if (path.isEmpty()) 264 | return; 265 | 266 | QPixmap map = m_stippleViewer->getImage(); 267 | map.save(path); 268 | m_params.saveParametersJSON(path); 269 | }); 270 | 271 | layout->addWidget(saveGroup); 272 | 273 | // start button 274 | QGroupBox* startGroup = new QGroupBox("Run Algorithm:", this); 275 | QVBoxLayout* startLayout = new QVBoxLayout(startGroup); 276 | 277 | QPushButton* startButton = new QPushButton("Start", this); 278 | startLayout->addWidget(startButton); 279 | 280 | connect(startButton, &QPushButton::released, 281 | [startButton]() { startButton->setEnabled(false); }); 282 | connect(startButton, &QPushButton::released, [this]() { disableSaveButtons(); }); 283 | connect(m_stippleViewer, &StippleViewer::finished, 284 | [startButton]() { startButton->setEnabled(true); }); 285 | connect(startButton, &QPushButton::released, [fileButton, jsonButton]() { 286 | fileButton->setEnabled(false); 287 | jsonButton->setEnabled(false); 288 | }); 289 | 290 | QProgressBar* progressBar = new QProgressBar(this); 291 | progressBar->setRange(0, 1); 292 | progressBar->setAlignment(Qt::AlignCenter); 293 | startLayout->addWidget(progressBar); 294 | startGroup->setLayout(startLayout); 295 | layout->addWidget(startGroup); 296 | 297 | connect(m_stippleViewer, &StippleViewer::finished, [fileButton, jsonButton, progressBar]() { 298 | fileButton->setEnabled(true); 299 | jsonButton->setEnabled(true); 300 | progressBar->setRange(0, 1); 301 | }); 302 | connect(startButton, &QPushButton::released, [this, progressBar]() { 303 | progressBar->setRange(0, 0); 304 | m_stippleViewer->stipple(m_params); 305 | }); 306 | 307 | layout->addStretch(1); 308 | } 309 | 310 | void SettingsWidget::enableSaveButtons() { 311 | m_savePNG->setEnabled(true); 312 | m_saveSVG->setEnabled(true); 313 | m_savePDF->setEnabled(true); 314 | } 315 | 316 | void SettingsWidget::disableSaveButtons() { 317 | m_savePNG->setEnabled(false); 318 | m_saveSVG->setEnabled(false); 319 | m_savePDF->setEnabled(false); 320 | } 321 | -------------------------------------------------------------------------------- /lbg-stippling/src/settingswidget.h: -------------------------------------------------------------------------------- 1 | #ifndef SETTINGSWIDGET_H 2 | #define SETTINGSWIDGET_H 3 | 4 | #include 5 | 6 | #include "lbgstippling.h" 7 | 8 | class StippleViewer; 9 | 10 | class SettingsWidget : public QWidget { 11 | 12 | public: 13 | explicit SettingsWidget(StippleViewer* stippleViewer, 14 | QWidget* parent, 15 | LBGStippling::Params& params); 16 | 17 | private: 18 | LBGStippling::Params m_params; 19 | StippleViewer* m_stippleViewer; 20 | 21 | QPushButton* m_savePNG; 22 | QPushButton* m_saveSVG; 23 | QPushButton* m_savePDF; 24 | 25 | void enableSaveButtons(); 26 | void disableSaveButtons(); 27 | }; 28 | 29 | #endif // SETTINGSWIDGET_H 30 | -------------------------------------------------------------------------------- /lbg-stippling/src/shader/Voronoi.frag.h: -------------------------------------------------------------------------------- 1 | std::string voronoiFragment = R"(#version 400 core 2 | 3 | in vec3 VertColor; 4 | 5 | out vec4 fragColor; 6 | 7 | void main() 8 | { 9 | fragColor = vec4(VertColor, 1); 10 | })"; 11 | -------------------------------------------------------------------------------- /lbg-stippling/src/shader/Voronoi.vert.h: -------------------------------------------------------------------------------- 1 | std::string voronoiVertex = R"(#version 400 core 2 | layout(location = 0) in vec3 VertPosition; 3 | layout(location = 1) in vec2 ConePosition; 4 | layout(location = 2) in vec3 ConeColor; 5 | 6 | out vec3 VertColor; 7 | 8 | 9 | const float height = 1.99f; 10 | 11 | const mat4 projection = mat4(2.0f, 0.0f, 0.0f, 0.0f, 12 | 0.0f, -2.0f, 0.0f, 0.0f, 13 | 0.0f, 0.0f, -1.0f, 0.0f, 14 | -1.0f, 1.0f, 0.0f, 1.0f); 15 | 16 | void main() 17 | { 18 | VertColor = ConeColor; 19 | gl_Position = projection * vec4(VertPosition.xy + ConePosition, VertPosition.z + (1.0 - height), 1.0f); 20 | })"; 21 | -------------------------------------------------------------------------------- /lbg-stippling/src/stippleviewer.cpp: -------------------------------------------------------------------------------- 1 | #include "stippleviewer.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | 11 | StippleViewer::StippleViewer(const QImage& img, QWidget* parent) 12 | : QGraphicsView(parent), m_image(img) { 13 | setInteractive(false); 14 | setRenderHint(QPainter::Antialiasing, true); 15 | setViewportUpdateMode(QGraphicsView::FullViewportUpdate); 16 | setOptimizationFlag(QGraphicsView::DontSavePainterState); 17 | setFrameStyle(0); 18 | setAttribute(Qt::WA_TranslucentBackground, false); 19 | setCacheMode(QGraphicsView::CacheBackground); 20 | 21 | setScene(new QGraphicsScene(this)); 22 | this->scene()->setSceneRect(m_image.rect()); 23 | this->scene()->setItemIndexMethod(QGraphicsScene::NoIndex); 24 | this->scene()->addPixmap(QPixmap::fromImage(m_image)); 25 | 26 | m_stippling = LBGStippling(); 27 | m_stippling.setStatusCallback([this](const auto& status) { 28 | this->scene()->clear(); 29 | emit iterationStatus(status.iteration + 1, status.size, status.splits, status.merges, 30 | status.hysteresis); 31 | }); 32 | m_stippling.setCellCallback([this](const auto& cells) { displayCells(cells); }); 33 | m_stippling.setStippleCallback([this](const auto& stipples) { displayPoints(stipples); }); 34 | } 35 | 36 | void StippleViewer::saveImage() 37 | { 38 | QString path = QString::number(m_count++) + ".png"; 39 | QPixmap map = this->getImage(); 40 | map.save(path); 41 | } 42 | 43 | void StippleViewer::displayPoints(const std::vector& stipples) { 44 | for (const auto& s : stipples) { 45 | auto x = s.pos.x() * m_image.width() - s.size / 2.0; 46 | auto y = s.pos.y() * m_image.height() - s.size / 2.0; 47 | this->scene()->addEllipse(x, y, s.size, s.size, Qt::NoPen, s.color); 48 | } 49 | // TODO: Fix event handling 50 | QCoreApplication::processEvents(); 51 | saveImage(); 52 | } 53 | 54 | void StippleViewer::displayCells(const IndexMap &map) 55 | { 56 | QImage image(map.width, map.height, QImage::Format_RGB32); 57 | for (size_t y = 0; y < map.height; ++y) { 58 | QRgb* line = reinterpret_cast(image.scanLine(y)); 59 | for (size_t x = 0; x < map.width; ++x) { 60 | size_t index = map.get(x, y); 61 | line[x] = index; 62 | } 63 | } 64 | this->scene()->addPixmap(QPixmap::fromImage(image)); 65 | // TODO: Fix event handling 66 | QCoreApplication::processEvents(); 67 | } 68 | 69 | QPixmap StippleViewer::getImage() { 70 | QPixmap pixmap(this->scene()->sceneRect().size().toSize()); 71 | pixmap.fill(Qt::white); 72 | QPainter painter(&pixmap); 73 | painter.setRenderHint(QPainter::Antialiasing, true); 74 | this->scene()->render(&painter); 75 | return pixmap; 76 | } 77 | 78 | void StippleViewer::saveImageSVG(const QString& path) { 79 | QSvgGenerator generator; 80 | generator.setFileName(path); 81 | generator.setSize(QSize(this->scene()->width(), this->scene()->height())); 82 | generator.setViewBox(this->scene()->sceneRect()); 83 | generator.setTitle("Stippling Result"); 84 | generator.setDescription("SVG File created by Weighted Linde-Buzo-Gray Stippling"); 85 | QPainter painter; 86 | painter.begin(&generator); 87 | this->scene()->render(&painter); 88 | painter.end(); 89 | } 90 | 91 | void StippleViewer::saveImagePDF(const QString& path) { 92 | adjustSize(); 93 | 94 | QPrinter printer(QPrinter::HighResolution); 95 | printer.setOutputFormat(QPrinter::PdfFormat); 96 | printer.setCreator("Weighted Linde-Buzo-Gray Stippling"); 97 | printer.setOutputFileName(path); 98 | printer.setPaperSize(m_image.size(), QPrinter::Point); 99 | printer.setFullPage(true); 100 | 101 | QPainter painter(&printer); 102 | this->render(&painter); 103 | } 104 | 105 | void StippleViewer::setInputImage(const QImage& img) { 106 | m_image = img; 107 | this->scene()->clear(); 108 | this->scene()->addPixmap(QPixmap::fromImage(m_image)); 109 | this->scene()->setSceneRect(m_image.rect()); 110 | 111 | auto w = m_image.width(); 112 | auto h = m_image.height(); 113 | 114 | setMinimumSize(w, h); 115 | adjustSize(); 116 | 117 | emit inputImageChanged(); 118 | } 119 | 120 | void StippleViewer::stipple(const LBGStippling::Params params) { 121 | // TODO: Handle return value 122 | m_stippling.stipple(m_image, params); 123 | finished(); 124 | } 125 | -------------------------------------------------------------------------------- /lbg-stippling/src/stippleviewer.h: -------------------------------------------------------------------------------- 1 | #ifndef STIPPLEVIEWER_H 2 | #define STIPPLEVIEWER_H 3 | 4 | #include 5 | 6 | #include "lbgstippling.h" 7 | 8 | class StippleViewer : public QGraphicsView { 9 | 10 | Q_OBJECT 11 | 12 | public: 13 | StippleViewer(const QImage& img, QWidget* parent); 14 | void stipple(const LBGStippling::Params params); 15 | QPixmap getImage(); 16 | void setInputImage(const QImage& img); 17 | void saveImageSVG(const QString& path); 18 | void saveImagePDF(const QString& path); 19 | void displayPoints(const std::vector& stipples); 20 | void displayCells(const IndexMap& cells); 21 | 22 | protected slots: 23 | void saveImage(); 24 | 25 | signals: 26 | void finished(); 27 | void inputImageChanged(); 28 | void iterationStatus(int iteration, int numberPoints, int splits, int merges, float hysteresis); 29 | 30 | private: 31 | LBGStippling m_stippling; 32 | QImage m_image; 33 | uint m_count = 0; 34 | }; 35 | 36 | #endif // STIPPLEVIEWER_H 37 | -------------------------------------------------------------------------------- /lbg-stippling/src/voronoicell.cpp: -------------------------------------------------------------------------------- 1 | #include "voronoicell.h" 2 | #include "voronoidiagram.h" 3 | 4 | #include 5 | 6 | struct Moments { 7 | float moment00; 8 | float moment10; 9 | float moment01; 10 | float moment11; 11 | float moment20; 12 | float moment02; 13 | }; 14 | 15 | std::vector accumulateCells(const IndexMap& map, const QImage& density) { 16 | // compute voronoi cell moments 17 | std::vector cells = std::vector(map.count()); 18 | std::vector moments = std::vector(map.count()); 19 | 20 | for (size_t x = 0; x < map.width; ++x) { 21 | for (size_t y = 0; y < map.height; ++y) { 22 | size_t index = map.get(x, y); 23 | 24 | QRgb densityPixel = density.pixel(x, y); 25 | float density = std::max(1.0f - qGray(densityPixel) / 255.0f, 26 | std::numeric_limits::epsilon()); 27 | 28 | VoronoiCell& cell = cells[index]; 29 | cell.area++; 30 | cell.sumDensity += density; 31 | 32 | Moments& m = moments[index]; 33 | m.moment00 += density; 34 | m.moment10 += x * density; 35 | m.moment01 += y * density; 36 | m.moment11 += x * y * density; 37 | m.moment20 += x * x * density; 38 | m.moment02 += y * y * density; 39 | } 40 | } 41 | 42 | // compute cell quantities 43 | for (size_t i = 0; i < cells.size(); ++i) { 44 | VoronoiCell& cell = cells[i]; 45 | if (cell.sumDensity <= 0.0f) 46 | continue; 47 | 48 | auto[m00, m10, m01, m11, m20, m02] = moments[i]; 49 | 50 | // centroid 51 | cell.centroid.setX(m10 / m00); 52 | cell.centroid.setY(m01 / m00); 53 | 54 | // orientation 55 | float x = m20 / m00 - cell.centroid.x() * cell.centroid.x(); 56 | float y = 2.0f * (m11 / m00 - cell.centroid.x() * cell.centroid.y()); 57 | float z = m02 / m00 - cell.centroid.y() * cell.centroid.y(); 58 | cell.orientation = std::atan2(y, x - z) / 2.0f; 59 | 60 | cell.centroid.setX((cell.centroid.x() + 0.5f) / density.width()); 61 | cell.centroid.setY((cell.centroid.y() + 0.5f) / density.height()); 62 | } 63 | return cells; 64 | } 65 | -------------------------------------------------------------------------------- /lbg-stippling/src/voronoicell.h: -------------------------------------------------------------------------------- 1 | #ifndef VORONOICELL_H 2 | #define VORONOICELL_H 3 | 4 | #include 5 | 6 | class IndexMap; 7 | 8 | struct VoronoiCell { 9 | QVector2D centroid; 10 | float orientation; 11 | float area; 12 | float sumDensity; 13 | }; 14 | 15 | std::vector accumulateCells(const IndexMap& map, const QImage& density); 16 | 17 | #endif // VORONOICELL_H 18 | -------------------------------------------------------------------------------- /lbg-stippling/src/voronoidiagram.cpp: -------------------------------------------------------------------------------- 1 | #include "voronoidiagram.h" 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "shader/Voronoi.frag.h" 12 | #include "shader/Voronoi.vert.h" 13 | 14 | #include 15 | 16 | const float pi = 3.14159265358979323846f; 17 | 18 | //////////////////////////////////////////////////////////////////////////////// 19 | /// Cell Encoder 20 | 21 | namespace CellEncoder { 22 | QVector3D encode(const uint32_t index) { 23 | uint32_t r = (index >> 16) & 0xff; 24 | uint32_t g = (index >> 8) & 0xff; 25 | uint32_t b = (index >> 0) & 0xff; 26 | return QVector3D(r / 255.0f, g / 255.0f, b / 255.0f); 27 | } 28 | 29 | uint32_t decode(const uint8_t& r, const uint8_t& g, const uint8_t& b) { 30 | return 0x00000000 | (r << 16) | (g << 8) | b; 31 | } 32 | } // namespace CellEncoder 33 | 34 | //////////////////////////////////////////////////////////////////////////////// 35 | /// Index Map 36 | 37 | IndexMap::IndexMap(size_t w, size_t h, size_t count) : width(w), height(h), m_numEncoded(count), m_data(w * h, 0) { 38 | } 39 | 40 | void IndexMap::set(size_t x, size_t y, uint32_t value) { 41 | m_data[y * width + x] = value; 42 | } 43 | 44 | uint32_t IndexMap::get(size_t x, size_t y) const { 45 | return m_data[y * width + x]; 46 | } 47 | 48 | size_t IndexMap::count() const { 49 | return m_numEncoded; 50 | } 51 | 52 | //////////////////////////////////////////////////////////////////////////////// 53 | /// Voronoi Diagram 54 | 55 | VoronoiDiagram::VoronoiDiagram(QImage& density) : m_densityMap(density) { 56 | m_context = new QOpenGLContext(); 57 | QSurfaceFormat format; 58 | format.setMajorVersion(3); 59 | format.setMinorVersion(3); 60 | format.setProfile(QSurfaceFormat::CoreProfile); 61 | m_context->setFormat(format); 62 | m_context->create(); 63 | 64 | m_surface = new QOffscreenSurface(Q_NULLPTR); 65 | m_surface->setFormat(m_context->format()); 66 | m_surface->create(); 67 | 68 | m_context->makeCurrent(m_surface); 69 | 70 | m_vao = new QOpenGLVertexArrayObject(m_context); 71 | m_vao->create(); 72 | 73 | m_shaderProgram = new QOpenGLShaderProgram(m_context); 74 | m_shaderProgram->addShaderFromSourceCode(QOpenGLShader::Vertex, voronoiVertex.c_str()); 75 | m_shaderProgram->addShaderFromSourceCode(QOpenGLShader::Fragment, voronoiFragment.c_str()); 76 | m_shaderProgram->link(); 77 | 78 | QOpenGLFramebufferObjectFormat fboFormat; 79 | fboFormat.setAttachment(QOpenGLFramebufferObject::Depth); 80 | m_fbo = new QOpenGLFramebufferObject(m_densityMap.width(), m_densityMap.height(), fboFormat); 81 | QVector cones = createConeDrawingData(m_densityMap.size()); 82 | 83 | m_vao->bind(); 84 | 85 | QOpenGLBuffer coneVBO = QOpenGLBuffer(QOpenGLBuffer::VertexBuffer); 86 | coneVBO.create(); 87 | coneVBO.setUsagePattern(QOpenGLBuffer::StaticDraw); 88 | coneVBO.bind(); 89 | coneVBO.allocate(cones.constData(), cones.size() * sizeof(QVector3D)); 90 | coneVBO.release(); 91 | 92 | m_shaderProgram->bind(); 93 | 94 | coneVBO.bind(); 95 | m_shaderProgram->enableAttributeArray(0); 96 | m_shaderProgram->setAttributeBuffer(0, GL_FLOAT, 0, 3); 97 | coneVBO.release(); 98 | 99 | m_shaderProgram->release(); 100 | 101 | m_vao->release(); 102 | } 103 | 104 | VoronoiDiagram::~VoronoiDiagram() { 105 | delete m_fbo; 106 | delete m_context; 107 | } 108 | 109 | IndexMap VoronoiDiagram::calculate(const QVector& points) { 110 | assert(!points.empty()); 111 | // QElapsedTimer progressTimer; 112 | // progressTimer.start(); 113 | 114 | m_context->makeCurrent(m_surface); 115 | 116 | QOpenGLFunctions_3_3_Core* gl = m_context->versionFunctions(); 117 | 118 | m_vao->bind(); 119 | 120 | m_shaderProgram->bind(); 121 | 122 | QOpenGLBuffer vboPositions = QOpenGLBuffer(QOpenGLBuffer::VertexBuffer); 123 | vboPositions.create(); 124 | vboPositions.setUsagePattern(QOpenGLBuffer::StaticDraw); 125 | vboPositions.bind(); 126 | vboPositions.allocate(points.constData(), points.size() * sizeof(QVector2D)); 127 | m_shaderProgram->enableAttributeArray(1); 128 | m_shaderProgram->setAttributeBuffer(1, GL_FLOAT, 0, 2); 129 | gl->glVertexAttribDivisor(1, 1); 130 | vboPositions.release(); 131 | 132 | std::vector colors(points.size()); 133 | std::generate(colors.begin(), 134 | colors.end(), [n = 0]() mutable { return CellEncoder::encode(n++); }); 135 | 136 | QOpenGLBuffer vboColors = QOpenGLBuffer(QOpenGLBuffer::VertexBuffer); 137 | vboColors.create(); 138 | vboColors.setUsagePattern(QOpenGLBuffer::StaticDraw); 139 | vboColors.bind(); 140 | vboColors.allocate(colors.data(), colors.size() * sizeof(QVector3D)); 141 | m_shaderProgram->enableAttributeArray(2); 142 | m_shaderProgram->setAttributeBuffer(2, GL_FLOAT, 0, 3); 143 | gl->glVertexAttribDivisor(2, 1); 144 | vboColors.release(); 145 | 146 | m_fbo->bind(); 147 | gl->glViewport(0, 0, m_densityMap.width(), m_densityMap.height()); 148 | gl->glDisable(GL_MULTISAMPLE); 149 | gl->glDisable(GL_DITHER); 150 | gl->glClampColor(GL_CLAMP_READ_COLOR, GL_FALSE); 151 | gl->glEnable(GL_DEPTH_TEST); 152 | gl->glClearColor(1.0f, 1.0f, 1.0f, 1.0f); 153 | gl->glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); 154 | gl->glDrawArraysInstanced(GL_TRIANGLE_FAN, 0, m_coneVertices, points.size()); 155 | 156 | m_shaderProgram->release(); 157 | m_vao->release(); 158 | 159 | QImage voronoiDiagram = m_fbo->toImage(); 160 | // voronoiDiagram.save("voronoiDiagram.png"); 161 | m_fbo->release(); 162 | // m_context->doneCurrent(); 163 | // qDebug() << "v calculate gl " << progressTimer.restart(); 164 | 165 | IndexMap idxMap(m_fbo->width(), m_fbo->height(), points.size()); 166 | 167 | #pragma omp parallel for 168 | for (int y = 0; y < m_fbo->height(); ++y) 169 | { 170 | QRgb *rowData = (QRgb*)voronoiDiagram.constScanLine(y); 171 | #pragma omp parallel for 172 | for (int x = 0; x < m_fbo->width(); ++x) 173 | { 174 | uint8_t r = qRed(rowData[x]); 175 | uint8_t g = qGreen(rowData[x]); 176 | uint8_t b = qBlue(rowData[x]); 177 | size_t index = CellEncoder::decode(r, g, b); 178 | // assert(index <= points.size()); 179 | idxMap.set(x, y, index); 180 | } 181 | } 182 | // qDebug() << "v calculate index map " << progressTimer.restart(); 183 | 184 | return idxMap; 185 | } 186 | 187 | // Calculate the number of slices required to ensure the given max. meshing error. 188 | // See "Fast Computation of Generalized Voronoi Diagram Using Graphics Hardware", 189 | // Hoff et. al., Proc. of SIGGRAPH 99. 190 | 191 | uint calcNumConeSlices(const float radius, const float maxError) { 192 | float alpha = 2.0f * std::acos((radius - maxError) / radius); 193 | return (unsigned)(2 * pi / alpha + 0.5f); 194 | } 195 | 196 | QVector VoronoiDiagram::createConeDrawingData(const QSize& size) { 197 | float radius = std::sqrt(2.0f); 198 | float maxError = 1.0f / (size.width() > size.height() ? size.width() : size.height()); 199 | uint numConeSlices = calcNumConeSlices(radius, maxError); 200 | 201 | float angleIncr = 2.0f * pi / numConeSlices; 202 | float height = 1.99f; 203 | 204 | QVector conePoints; 205 | conePoints.push_back(QVector3D(0.0f, 0.0f, height)); 206 | 207 | float aspect = (float)size.width() / size.height(); 208 | 209 | for (uint i = 0; i < numConeSlices; ++i) { 210 | conePoints.push_back(QVector3D(radius * std::cos(i * angleIncr), 211 | aspect * radius * std::sin(i * angleIncr), height - radius)); 212 | } 213 | 214 | conePoints.push_back(QVector3D(radius, 0.0f, height - radius)); 215 | 216 | m_coneVertices = conePoints.size(); 217 | return conePoints; 218 | } 219 | -------------------------------------------------------------------------------- /lbg-stippling/src/voronoidiagram.h: -------------------------------------------------------------------------------- 1 | #ifndef VORONOIDIAGRAM_H 2 | #define VORONOIDIAGRAM_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class IndexMap { 12 | public: 13 | size_t width; 14 | size_t height; 15 | 16 | IndexMap()=default; 17 | IndexMap(size_t w, size_t h, size_t count); 18 | void set(size_t x, size_t y, uint32_t value); 19 | uint32_t get(size_t x, size_t y) const; 20 | size_t count() const; 21 | 22 | private: 23 | size_t m_numEncoded; 24 | std::vector m_data; 25 | }; 26 | 27 | class VoronoiDiagram { 28 | 29 | public: 30 | VoronoiDiagram(QImage& density); 31 | ~VoronoiDiagram(); 32 | 33 | IndexMap calculate(const QVector& points); 34 | 35 | private: 36 | int m_coneVertices; 37 | 38 | QOpenGLContext* m_context; 39 | QOffscreenSurface* m_surface; 40 | QOpenGLVertexArrayObject* m_vao; 41 | QOpenGLShaderProgram* m_shaderProgram; 42 | QOpenGLFramebufferObject* m_fbo; 43 | QImage m_densityMap; 44 | 45 | QVector createConeDrawingData(const QSize& size); 46 | }; 47 | 48 | #endif // VORONOIDIAGRAM_H 49 | -------------------------------------------------------------------------------- /lib/tobii_research.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbruder/FoveatedVolumeRendering/81339beb423f0048ad680e855a25e9d486ee3cb3/lib/tobii_research.dll -------------------------------------------------------------------------------- /lib/tobii_research.lib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbruder/FoveatedVolumeRendering/81339beb423f0048ad680e855a25e9d486ee3cb3/lib/tobii_research.lib -------------------------------------------------------------------------------- /resources/eyeTracker.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbruder/FoveatedVolumeRendering/81339beb423f0048ad680e855a25e9d486ee3cb3/resources/eyeTracker.jpg -------------------------------------------------------------------------------- /resources/jet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbruder/FoveatedVolumeRendering/81339beb423f0048ad680e855a25e9d486ee3cb3/resources/jet.png -------------------------------------------------------------------------------- /resources/mc1024_92702.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbruder/FoveatedVolumeRendering/81339beb423f0048ad680e855a25e9d486ee3cb3/resources/mc1024_92702.zip -------------------------------------------------------------------------------- /resources/richtmyer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbruder/FoveatedVolumeRendering/81339beb423f0048ad680e855a25e9d486ee3cb3/resources/richtmyer.png -------------------------------------------------------------------------------- /resources/vortex.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbruder/FoveatedVolumeRendering/81339beb423f0048ad680e855a25e9d486ee3cb3/resources/vortex.png -------------------------------------------------------------------------------- /resources/zeiss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbruder/FoveatedVolumeRendering/81339beb423f0048ad680e855a25e9d486ee3cb3/resources/zeiss.png -------------------------------------------------------------------------------- /screenshots/2017-12-19-cham.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbruder/FoveatedVolumeRendering/81339beb423f0048ad680e855a25e9d486ee3cb3/screenshots/2017-12-19-cham.png -------------------------------------------------------------------------------- /screenshots/2017-12-19-nova.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbruder/FoveatedVolumeRendering/81339beb423f0048ad680e855a25e9d486ee3cb3/screenshots/2017-12-19-nova.png -------------------------------------------------------------------------------- /screenshots/2018-04-23-cham.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbruder/FoveatedVolumeRendering/81339beb423f0048ad680e855a25e9d486ee3cb3/screenshots/2018-04-23-cham.png -------------------------------------------------------------------------------- /screenshots/2018-04-23-nova.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbruder/FoveatedVolumeRendering/81339beb423f0048ad680e855a25e9d486ee3cb3/screenshots/2018-04-23-nova.png -------------------------------------------------------------------------------- /src/io/datrawreader.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * 4 | * \author Valentin Bruder 5 | * 6 | * \copyright Copyright (C) 2018 Valentin Bruder 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Lesser General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Lesser General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Lesser General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | #include 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | /* 35 | * DatRawReader::read_files 36 | */ 37 | void DatRawReader::read_files(const std::string file_name) 38 | { 39 | // check file 40 | if (file_name.empty()) 41 | throw std::invalid_argument("File name must not be empty."); 42 | 43 | try 44 | { 45 | // we have a dat file where the binary files are specified 46 | if (file_name.substr(file_name.find_last_of(".") + 1) == "dat") 47 | { 48 | _prop.dat_file_name = file_name; 49 | this->read_dat(_prop.dat_file_name); 50 | } 51 | else // we only have the raw binary file 52 | { 53 | _prop.raw_file_names.clear(); 54 | std::cout << "Trying to read binary data directly from " << file_name << std::endl; 55 | this->_prop.raw_file_names.push_back(file_name); 56 | } 57 | this->_raw_data.clear(); 58 | for (const auto &n : _prop.raw_file_names) 59 | read_raw(n); 60 | } 61 | catch (std::runtime_error e) 62 | { 63 | throw e; 64 | } 65 | } 66 | 67 | 68 | /* 69 | * DatRawReader::has_data 70 | */ 71 | bool DatRawReader::has_data() const 72 | { 73 | return !(_raw_data.empty()); 74 | } 75 | 76 | 77 | /* 78 | * DatRawReader::data 79 | */ 80 | const std::vector > & DatRawReader::data() const 81 | { 82 | if (!has_data()) 83 | { 84 | throw std::runtime_error("No data available."); 85 | } 86 | return _raw_data; 87 | } 88 | 89 | /** 90 | * @brief DatRawReader::properties 91 | * @return 92 | */ 93 | const Properties &DatRawReader::properties() const 94 | { 95 | if (!has_data()) 96 | { 97 | throw std::runtime_error("No properties of volume data set available."); 98 | } 99 | return _prop; 100 | } 101 | 102 | /** 103 | * @brief DatRawReader::clearData 104 | */ 105 | void DatRawReader::clearData() 106 | { 107 | _raw_data.clear(); 108 | } 109 | 110 | 111 | /* 112 | * DatRawReader::read_dat 113 | */ 114 | void DatRawReader::read_dat(const std::string dat_file_name) 115 | { 116 | std::ifstream dat_file(dat_file_name); 117 | std::string line; 118 | std::vector> lines; 119 | 120 | // read lines from .dat file and split on whitespace 121 | if (dat_file.is_open()) 122 | { 123 | while (std::getline(dat_file, line)) 124 | { 125 | std::istringstream iss(line); 126 | std::vector tokens; 127 | std::copy(std::istream_iterator(iss), 128 | std::istream_iterator(), 129 | std::back_inserter(tokens)); 130 | lines.push_back(tokens); 131 | } 132 | dat_file.close(); 133 | } 134 | else 135 | { 136 | throw std::runtime_error("Could not open .dat file " + dat_file_name); 137 | } 138 | 139 | bool setSliceThickness = false; 140 | for (auto l : lines) 141 | { 142 | if (!l.empty()) 143 | { 144 | std::string name = l.at(0); 145 | if (name.find("ObjectFileName") != std::string::npos && l.size() > 1u) 146 | { 147 | _prop.raw_file_names.clear(); 148 | for (const auto &s : l) 149 | { 150 | if (s.find("ObjectFileName") == std::string::npos) 151 | _prop.raw_file_names.push_back(s); 152 | } 153 | } 154 | else if (name.find("Resolution") != std::string::npos && l.size() > 3u) 155 | { 156 | for (size_t i = 1; i < std::min(l.size(), static_cast(5)); ++i) 157 | { 158 | _prop.volume_res.at(i - 1) = std::stoul(l.at(i)); 159 | } 160 | } 161 | else if (name.find("SliceThickness") != std::string::npos && l.size() > 3u) 162 | { 163 | // slice thickness in x,y,z dimension 164 | for (size_t i = 1; i < 4; ++i) 165 | { 166 | double thickness = std::stod(l.at(i)); 167 | // HACK for locale with ',' instread of '.' as decimal separator 168 | if (thickness <= 0) 169 | std::replace(l.at(i).begin(), l.at(i).end(), '.', ','); 170 | _prop.slice_thickness.at(i - 1) = std::stod(l.at(i)); 171 | } 172 | setSliceThickness = true; 173 | } 174 | else if (name.find("Format") != std::string::npos && l.size() > 1u) 175 | { 176 | _prop.format = l.at(1); 177 | } 178 | else if (name.find("Nodes") != std::string::npos && l.size() > 1u) 179 | { 180 | _prop.node_file_name = l.at(1); 181 | } 182 | else if (name.find("TimeSeries") != std::string::npos && l.size() > 1u) 183 | { 184 | _prop.volume_res.at(3) = std::stoul(l.at(1)); 185 | } 186 | } 187 | } 188 | 189 | // check values read from the dat file 190 | if (_prop.raw_file_names.empty()) 191 | { 192 | throw std::runtime_error("Missing raw file names declaration in " + dat_file_name); 193 | } 194 | if (_prop.raw_file_names.size() < _prop.volume_res.at(3)) 195 | { 196 | std::size_t first = _prop.raw_file_names.at(0).find_first_of("0123456789"); 197 | std::size_t last = _prop.raw_file_names.at(0).find_last_of("0123456789"); 198 | int number = stoi(_prop.raw_file_names.at(0).substr(first, last)); 199 | int digits = last-first + 1; 200 | std::string base = _prop.raw_file_names.at(0).substr(0, first); 201 | for (std::size_t i = 0; i < _prop.volume_res.at(3) - 1; ++i) 202 | { 203 | ++number; 204 | std::stringstream ss; 205 | ss << base << std::setw(digits) << std::setfill('0') << number; 206 | _prop.raw_file_names.push_back(ss.str()); 207 | } 208 | } 209 | if (std::any_of(std::begin(_prop.volume_res), 210 | std::end(_prop.volume_res), [](int i){return i == 0;})) 211 | { 212 | std::cerr << "WARNING: Missing resolution declaration in " << dat_file_name << std::endl; 213 | std::cerr << "Trying to calculate the volume resolution from raw file size, " 214 | << "assuming equal resolution in each dimension." 215 | << std::endl; 216 | } 217 | if (!setSliceThickness) 218 | { 219 | std::cerr << "WARNING: Missing slice thickness declaration in " << dat_file_name 220 | << std::endl; 221 | std::cerr << "Assuming a slice thickness of 1.0 in each dimension." << std::endl; 222 | _prop.slice_thickness.fill(1.0); 223 | } 224 | if (_prop.format.empty()) 225 | { 226 | if (!std::any_of(std::begin(_prop.volume_res), 227 | std::end(_prop.volume_res), [](int i){return i == 0;})) 228 | { 229 | std::cerr << "WARNING: Missing format declaration in " << dat_file_name << std::endl; 230 | std::cerr << "Trying to calculate the format from raw file size and volume resolution." 231 | << std::endl; 232 | } 233 | else 234 | { 235 | std::cerr << "WARNING: Missing format declaration in " << dat_file_name << std::endl; 236 | std::cerr << "Assuming UCHAR format." << std::endl; 237 | } 238 | } 239 | } 240 | 241 | /* 242 | * DatRawReader::read_raw 243 | */ 244 | void DatRawReader::read_raw(const std::string raw_file_name) 245 | { 246 | if (raw_file_name.empty()) 247 | throw std::invalid_argument("Raw file name must not be empty."); 248 | 249 | // append .raw file name to .dat file name path 250 | std::size_t found = _prop.dat_file_name.find_last_of("/\\"); 251 | std::string name_with_path; 252 | if (found != std::string::npos && _prop.dat_file_name.size() >= found) 253 | { 254 | name_with_path = _prop.dat_file_name.substr(0, found + 1) + raw_file_name; 255 | } 256 | else 257 | { 258 | name_with_path = raw_file_name; 259 | } 260 | 261 | // use plain old C++ method for file read here that is much faster than iterator 262 | // based approaches according to: 263 | // http://insanecoding.blogspot.de/2011/11/how-to-read-in-file-in-c.html 264 | std::ifstream is(name_with_path, std::ios::in | std::ifstream::binary); 265 | if (is) 266 | { 267 | // get length of file: 268 | is.seekg(0, is.end); 269 | //#ifdef _WIN32 270 | // // HACK: to support files bigger than 2048 MB on windows 271 | // _prop.raw_file_size = *(__int64 *)(((char *)&(is.tellg())) + 8); 272 | //#else 273 | _prop.raw_file_size = static_cast(is.tellg()); 274 | //#endif 275 | is.seekg( 0, is.beg ); 276 | 277 | std::vector raw_timestep; 278 | raw_timestep.resize(_prop.raw_file_size); 279 | 280 | // read data as a block: 281 | is.read(raw_timestep.data(), static_cast(_prop.raw_file_size)); 282 | _raw_data.push_back(std::move(raw_timestep)); 283 | 284 | if (!is) 285 | throw std::runtime_error("Error reading " + raw_file_name); 286 | is.close(); 287 | } 288 | else 289 | { 290 | throw std::runtime_error("Could not open " + raw_file_name); 291 | } 292 | 293 | // if resolution was not specified, try to calculate from file size 294 | if (!_raw_data.empty() && std::any_of(std::begin(_prop.volume_res), 295 | std::end(_prop.volume_res), [](int i){return i == 0;})) 296 | { 297 | infer_volume_resolution(_prop.raw_file_size); 298 | } 299 | 300 | // if format was not specified in .dat file, try to calculate from 301 | // file size and volume resolution 302 | if (_prop.format.empty() && !_raw_data.empty() 303 | && std::none_of(std::begin(_prop.volume_res), 304 | std::end(_prop.volume_res), [](int i){return i == 0;})) 305 | { 306 | unsigned int bytes = _raw_data.at(0).size() / (static_cast(_prop.volume_res[0]) * 307 | static_cast(_prop.volume_res[1]) * 308 | static_cast(_prop.volume_res[2])); 309 | switch (bytes) 310 | { 311 | case 1: 312 | _prop.format = "UCHAR"; 313 | std::cout << "Format determined as UCHAR." << std::endl; 314 | break; 315 | case 2: 316 | _prop.format = "USHORT"; 317 | std::cout << "Format determined as USHORT." << std::endl; 318 | break; 319 | case 4: 320 | _prop.format = "FLOAT"; 321 | std::cout << "Format determined as FLOAT." << std::endl; 322 | break; 323 | default: throw std::runtime_error("Could not resolve missing format specification."); 324 | } 325 | } 326 | } 327 | 328 | /** 329 | * DatRawReader::infer_volume_resolution 330 | */ 331 | void DatRawReader::infer_volume_resolution(unsigned long long file_size) 332 | { 333 | std::cout << "WARNING: Trying to infer volume resolution from data size, assuming equal dimensions." 334 | << std::endl; 335 | if (_prop.format.empty()) 336 | { 337 | std::cout << "WARNING: Format could not be determined, assuming UCHAR" << std::endl; 338 | _prop.format = "UCHAR"; 339 | } 340 | 341 | if (_prop.format == "UCHAR") 342 | file_size /= 1; 343 | else if (_prop.format == "USHORT") 344 | file_size /= 2; 345 | else if (_prop.format == "FLOAT") 346 | file_size /= 4; 347 | 348 | unsigned int cuberoot = static_cast(std::cbrt(file_size)); 349 | _prop.volume_res.at(0) = cuberoot; 350 | _prop.volume_res.at(1) = cuberoot; 351 | _prop.volume_res.at(2) = cuberoot; 352 | } 353 | -------------------------------------------------------------------------------- /src/io/datrawreader.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * 4 | * \author Valentin Bruder 5 | * 6 | * \copyright Copyright (C) 2018 Valentin Bruder 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Lesser General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Lesser General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Lesser General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | #pragma once 24 | 25 | #include 26 | #include 27 | #include 28 | 29 | /// 30 | /// The Properties struct that can hold .dat and .raw file information. 31 | /// 32 | struct Properties 33 | { 34 | std::string dat_file_name; 35 | std::vector raw_file_names; 36 | size_t raw_file_size = 0; 37 | 38 | std::array volume_res = {{0, 0, 0, 1}}; // x, y, z, t 39 | std::array slice_thickness = {{1.0, 1.0, 1.0}}; 40 | std::string format = ""; // UCHAR, USHORT, FLOAT 41 | std::string node_file_name = ""; 42 | unsigned int time_series = {1}; 43 | 44 | const std::string to_string() const 45 | { 46 | std::string str("Resolution: "); 47 | for (auto v : volume_res) 48 | { 49 | str += std::to_string(v) + " "; 50 | } 51 | str += "| Slice Thickness: "; 52 | for (auto v : slice_thickness) 53 | { 54 | str += std::to_string(v) + " "; 55 | } 56 | str += "| Format: " + format; 57 | return str; 58 | } 59 | }; 60 | 61 | /// 62 | /// Dat-raw volume data file reader. 63 | /// Based on a description in a text file ".dat", raw voxel data is read from a 64 | /// binary file ".raw". The dat-file should contain information on the file name of the 65 | /// raw-file, the resolution of the volume, the data format of the scalar data and possibly 66 | /// the slice thickness (default is 1.0 in each dimension). 67 | /// The raw data is stored in a vector of chars. 68 | /// 69 | class DatRawReader 70 | { 71 | 72 | public: 73 | 74 | /// 75 | /// Read the dat file of the given name and based on the content, the raw data. 76 | /// Saves volume data set properties and scalar data in member variables. 77 | /// 78 | /// Name and full path of the dat file 79 | /// Reference to a vector where the read raw data 80 | /// is stored in. 81 | /// If one of the files could not be found or read. 85 | /// Get the read status of hte objects. 86 | /// 87 | /// true if raw data has been read, false otherwise. 88 | bool has_data() const; 89 | 90 | /// 91 | /// Get a constant reference to the raw data that has been read. 92 | /// 93 | /// If no raw data has been read before. 94 | const std::vector > &data() const; 95 | 96 | /// 97 | /// Get a constant reference to the volume data set properties that have been read. 98 | /// 99 | /// If no data has been read before. 100 | const Properties &properties() const; 101 | 102 | /// 103 | /// \brief clearData 104 | /// 105 | void clearData(); 106 | 107 | private: 108 | 109 | /// 110 | /// Tries to infer the volume data resolution from the file size. 111 | /// Assumes equal size in each dimension and UCHAr format if not specified otherwise. 112 | /// 113 | /// File size in bytes. 114 | void infer_volume_resolution(unsigned long long file_size); 115 | 116 | /// 117 | /// Read the dat textfile. 118 | /// 119 | /// Name and full path of the dat text file. 120 | /// If dat file cannot be opened or properties are missing. 121 | void read_dat(const std::string dat_file_name); 122 | 123 | /// 124 | /// Read scalar voxel data from a given raw file. 125 | /// 126 | /// This method does not check for a valid file name except for an assertion 127 | /// that it is not empty. 128 | /// Name of the raw data file without the path. 129 | /// If the given file could not be opened or read. 130 | void read_raw(const std::string raw_file_name); 131 | 132 | /// 133 | /// Properties of the volume data set. 134 | /// 135 | Properties _prop; 136 | 137 | /// 138 | /// The raw voxel data. 139 | /// 140 | std::vector > _raw_data; 141 | }; 142 | -------------------------------------------------------------------------------- /src/oclutil/openclglutilities.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is based on Erik Smithad's OpenCLUtilityLibrary 3 | * (https://github.com/smistad/OpenCLUtilityLibrary) with the following license: 4 | * 5 | * Copyright (c) SINTEF, 2014 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright notice, this 12 | * list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 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 OWNER OR CONTRIBUTORS BE LIABLE FOR 21 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | * 28 | * The views and conclusions contained in the software and documentation are those 29 | * of the authors and should not be interpreted as representing official policies, 30 | * either expressed or implied, of SINTEF. 31 | * 32 | */ 33 | 34 | #include "src/oclutil/openclglutilities.h" 35 | 36 | cl::Context createCLGLContext(std::string &device_name, cl_device_type type, cl_vendor vendor) 37 | { 38 | cl::Platform platform = getPlatform(type, vendor); 39 | 40 | //Creating the context 41 | #if defined(__APPLE__) || defined(__MACOSX) 42 | // Apple (untested) 43 | cl_context_properties cps[] = { 44 | CL_CONTEXT_PROPERTY_USE_CGL_SHAREGROUP_APPLE, 45 | (cl_context_properties)CGLGetShareGroup(CGLGetCurrentContext()), 46 | 0}; 47 | 48 | 49 | #else 50 | #ifdef _WIN32 51 | // Windows 52 | cl_context_properties cps[] = { 53 | CL_GL_CONTEXT_KHR, 54 | (cl_context_properties)wglGetCurrentContext(), 55 | CL_WGL_HDC_KHR, 56 | (cl_context_properties)wglGetCurrentDC(), 57 | CL_CONTEXT_PLATFORM, 58 | (cl_context_properties)(platform)(), 59 | 0 60 | }; 61 | #else 62 | // Linux 63 | cl_context_properties cps[] = { 64 | CL_GL_CONTEXT_KHR, 65 | (cl_context_properties)glXGetCurrentContext(), 66 | CL_GLX_DISPLAY_KHR, 67 | (cl_context_properties)glXGetCurrentDisplay(), 68 | CL_CONTEXT_PLATFORM, 69 | (cl_context_properties)(platform)(), 70 | 0 71 | }; 72 | #endif 73 | #endif 74 | 75 | try 76 | { 77 | // We need to check if there is more than one device first 78 | std::vector devices; 79 | std::vector singleDevice; 80 | platform.getDevices(type, &devices); 81 | cl::Context context; 82 | 83 | // If more than one CL device, find out which one is associated with GL context 84 | if(devices.size() > 1) 85 | { 86 | #if !(defined(__APPLE__) || defined(__MACOSX)) 87 | cl::Device interopDevice = getValidGLCLInteropDevice(platform, cps); 88 | singleDevice.push_back(interopDevice); 89 | context = cl::Context(singleDevice, cps); 90 | std::cout << "Using device: " << interopDevice.getInfo() << std::endl; 91 | device_name = interopDevice.getInfo(); 92 | #else 93 | context = cl::Context(type,cps); 94 | #endif 95 | } 96 | else 97 | { 98 | context = cl::Context(type, cps); 99 | } 100 | return context; 101 | } 102 | catch(cl::Error error) 103 | { 104 | std::cerr << "Error in " << error.what() << "(" << getCLErrorString(error.err()) << ")" 105 | << std::endl; 106 | throw std::runtime_error("Failed to create an OpenCL context from the OpenGL context. Make \ 107 | sure the displaying device is your selected compute device, \ 108 | otherwise the context sharing won't work."); 109 | } 110 | } 111 | 112 | #if !(defined(__APPLE__) || defined(__MACOSX)) 113 | cl::Device getValidGLCLInteropDevice(cl::Platform platform, cl_context_properties* properties) 114 | { 115 | // Function for finding a valid device for CL-GL context. 116 | // Thanks to Jim Vaughn for this contribution 117 | // cl::Device displayDevice; 118 | cl_device_id interopDeviceId; 119 | 120 | int status; 121 | size_t deviceSize = 0; 122 | 123 | // Load extension function call 124 | clGetGLContextInfoKHR_fn glGetGLContextInfo_func = 125 | (clGetGLContextInfoKHR_fn)clGetExtensionFunctionAddressForPlatform(platform(), 126 | "clGetGLContextInfoKHR"); 127 | 128 | // Ask for the CL device associated with the GL context 129 | status = glGetGLContextInfo_func(properties, 130 | CL_CURRENT_DEVICE_FOR_GL_CONTEXT_KHR, 131 | sizeof(cl_device_id), 132 | &interopDeviceId, 133 | &deviceSize); 134 | 135 | if(deviceSize == 0) 136 | { 137 | throw cl::Error(1, "No OpenGL capable devices found for current platform."); 138 | } 139 | if(status != CL_SUCCESS) 140 | { 141 | throw cl::Error(1, "Could not get CL-GL interop device for the current platform. \ 142 | Failure occured during call to clGetGLContextInfoKHR."); 143 | } 144 | 145 | return cl::Device(interopDeviceId); 146 | } 147 | #endif 148 | -------------------------------------------------------------------------------- /src/oclutil/openclglutilities.h: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is based on Erik Smithad's OpenCLUtilityLibrary 3 | * (https://github.com/smistad/OpenCLUtilityLibrary) with the following license: 4 | * 5 | * Copyright (c) SINTEF, 2014 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright notice, this 12 | * list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 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 OWNER OR CONTRIBUTORS BE LIABLE FOR 21 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | * 28 | * The views and conclusions contained in the software and documentation are those 29 | * of the authors and should not be interpreted as representing official policies, 30 | * either expressed or implied, of SINTEF. 31 | * 32 | */ 33 | 34 | #pragma once 35 | 36 | #include "src/oclutil/openclutilities.h" 37 | 38 | #if defined (__APPLE__) || defined(MACOSX) 39 | #define GL_SHARING_EXTENSION "cl_APPLE_gl_sharing" 40 | #else 41 | #define GL_SHARING_EXTENSION "cl_khr_gl_sharing" 42 | #endif 43 | 44 | #if defined(__APPLE__) || defined(__MACOSX) 45 | #include 46 | #include 47 | #elif _WIN32 48 | #include 49 | #include 50 | #include 51 | #include 52 | 53 | #pragma comment(lib, "opengl32.lib") 54 | #pragma comment(lib, "glu32.lib") 55 | #pragma warning( disable : 4996) 56 | #else // Linux 57 | #include 58 | #include 59 | #endif 60 | 61 | 62 | cl::Context createCLGLContext(std::string &device_name, 63 | cl_device_type type = CL_DEVICE_TYPE_ALL, 64 | cl_vendor vendor = VENDOR_ANY); 65 | 66 | #if !(defined(__APPLE__) || defined(__MACOSX)) 67 | cl::Device getValidGLCLInteropDevice(cl::Platform platform, cl_context_properties* properties); 68 | #endif 69 | 70 | -------------------------------------------------------------------------------- /src/oclutil/openclutilities.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is based on Erik Smithad's OpenCLUtilityLibrary 3 | * (https://github.com/smistad/OpenCLUtilityLibrary) with the following license: 4 | * 5 | * Copyright (c) SINTEF, 2014 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright notice, this 12 | * list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 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 OWNER OR CONTRIBUTORS BE LIABLE FOR 21 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | * 28 | * The views and conclusions contained in the software and documentation are those 29 | * of the authors and should not be interpreted as representing official policies, 30 | * either expressed or implied, of SINTEF. 31 | * 32 | */ 33 | 34 | #include "src/oclutil/openclutilities.h" 35 | 36 | #ifdef WIN32 37 | #else 38 | #include 39 | #include 40 | #endif 41 | 42 | cl::Platform getPlatform(cl_device_type type, cl_vendor vendor) 43 | { 44 | // Get available platforms 45 | std::vector platforms; 46 | cl::Platform::get(&platforms); 47 | 48 | if(platforms.size() == 0) 49 | throw cl::Error(1, "No OpenCL platforms were found"); 50 | 51 | int platformID = -1; 52 | if(vendor != VENDOR_ANY) 53 | { 54 | std::string find; 55 | switch(vendor) 56 | { 57 | case VENDOR_NVIDIA: 58 | find = "NVIDIA"; 59 | break; 60 | case VENDOR_AMD: 61 | find = "Advanced Micro Devices"; 62 | break; 63 | case VENDOR_INTEL: 64 | find = "Intel"; 65 | break; 66 | default: 67 | throw cl::Error(1, "Invalid vendor specified"); 68 | break; 69 | } 70 | 71 | std::cout << platforms.size() << " OpenCL platforms found: " << std::endl; 72 | for(unsigned int i = 1; i <= platforms.size(); i++) 73 | std::cout << " " << i << ") " 74 | << platforms[i-1].getInfo() << std::endl; 75 | 76 | for(unsigned int i = 0; i < platforms.size(); i++) 77 | { 78 | if(platforms[i].getInfo().find(find) != std::string::npos) 79 | { 80 | try { 81 | std::vector devices; 82 | platforms[i].getDevices(type, &devices); 83 | platformID = i; 84 | break; 85 | } catch(cl::Error e) { 86 | continue; 87 | } 88 | } 89 | } 90 | } else 91 | { 92 | for(unsigned int i = 0; i < platforms.size(); i++) 93 | { 94 | try 95 | { 96 | std::vector devices; 97 | platforms[i].getDevices(type, &devices); 98 | platformID = i; 99 | break; 100 | } catch(cl::Error e) { 101 | continue; 102 | } 103 | } 104 | } 105 | 106 | if(platformID == -1) 107 | throw cl::Error(1, "No compatible OpenCL platform found"); 108 | 109 | cl::Platform platform = platforms[platformID]; 110 | std::cout << "Using platform: " << platform.getInfo() << std::endl; 111 | std::cout << "From vendor: " << platform.getInfo() << std::endl; 112 | return platform; 113 | } 114 | 115 | 116 | cl::Context createCLContextFromArguments(int argc, char ** argv) 117 | { 118 | cl_device_type type = CL_DEVICE_TYPE_ALL; 119 | cl_vendor vendor = VENDOR_ANY; 120 | 121 | for(int i = 0; i < argc; i++) { 122 | if(strcmp(argv[i], "--device") == 0) { 123 | if(strcmp(argv[i+1], "cpu") == 0) { 124 | type = CL_DEVICE_TYPE_CPU; 125 | } else if(strcmp(argv[i+1], "gpu") == 0) { 126 | type = CL_DEVICE_TYPE_GPU; 127 | } 128 | i++; 129 | } else if(strcmp(argv[i], "--vendor") == 0) { 130 | if(strcmp(argv[i+1], "amd") == 0) { 131 | vendor = VENDOR_AMD; 132 | } else if(strcmp(argv[i+1], "intel") == 0) { 133 | vendor = VENDOR_INTEL; 134 | } else if(strcmp(argv[i+1], "nvidia") == 0) { 135 | vendor = VENDOR_NVIDIA; 136 | } 137 | i++; 138 | } 139 | } 140 | 141 | return createCLContext(type, vendor); 142 | } 143 | 144 | 145 | cl::Context createCLContext(cl_device_type type, cl_vendor vendor) 146 | { 147 | cl::Platform platform = getPlatform(type, vendor); 148 | 149 | // Use the preferred platform and create a context 150 | cl_context_properties cps[] = { 151 | CL_CONTEXT_PLATFORM, 152 | (cl_context_properties)(platform)(), 153 | 0 154 | }; 155 | 156 | try { 157 | cl::Context context = cl::Context(type, cps); 158 | return context; 159 | } catch(cl::Error error) { 160 | throw cl::Error(1, "Failed to create an OpenCL context!"); 161 | } 162 | } 163 | 164 | cl::Context createCLContext(std::vector devices) 165 | { 166 | try { 167 | cl::Context context = cl::Context(devices); 168 | return context; 169 | } catch(cl::Error error) { 170 | throw cl::Error(1, "Failed to create an OpenCL context!"); 171 | } 172 | } 173 | 174 | 175 | cl::Program buildProgramFromSource(cl::Context context, const std::string &filename, 176 | const std::string &buildOptions) 177 | { 178 | // Read source file 179 | std::ifstream sourceFile(filename.c_str()); 180 | if(sourceFile.fail()) 181 | throw std::invalid_argument("Failed to open OpenCL kernel file " + filename); 182 | std::string sourceCode(std::istreambuf_iterator(sourceFile), 183 | (std::istreambuf_iterator())); 184 | //cl::Program::Sources source(1, std::make_pair(sourceCode.c_str(), sourceCode.length()+1)); 185 | 186 | // Make program of the source code in the context 187 | cl::Program program = cl::Program(context, sourceCode); 188 | 189 | std::vector devices = context.getInfo(); 190 | 191 | // Build program for these specific devices 192 | try { 193 | program.build(devices, buildOptions.c_str()); 194 | } catch(cl::Error error) 195 | { 196 | if(error.err() == CL_BUILD_PROGRAM_FAILURE) 197 | { 198 | std::cout << "Build log:" << std::endl 199 | << program.getBuildInfo(devices[0]) << std::endl; 200 | } 201 | throw error; 202 | } 203 | return program; 204 | } 205 | 206 | 207 | std::string getCLErrorString(cl_int err) 208 | { 209 | switch (err) { 210 | case CL_SUCCESS: return "Success!"; 211 | case CL_DEVICE_NOT_FOUND: return "Device not found."; 212 | case CL_DEVICE_NOT_AVAILABLE: return "Device not available"; 213 | case CL_COMPILER_NOT_AVAILABLE: return "Compiler not available"; 214 | case CL_MEM_OBJECT_ALLOCATION_FAILURE: return "Memory object allocation failure"; 215 | case CL_OUT_OF_RESOURCES: return "Out of resources"; 216 | case CL_OUT_OF_HOST_MEMORY: return "Out of host memory"; 217 | case CL_PROFILING_INFO_NOT_AVAILABLE: return "Profiling information not available"; 218 | case CL_MEM_COPY_OVERLAP: return "Memory copy overlap"; 219 | case CL_IMAGE_FORMAT_MISMATCH: return "Image format mismatch"; 220 | case CL_IMAGE_FORMAT_NOT_SUPPORTED: return "Image format not supported"; 221 | case CL_BUILD_PROGRAM_FAILURE: return "Program build failure"; 222 | case CL_MAP_FAILURE: return "Map failure"; 223 | case CL_INVALID_VALUE: return "Invalid value"; 224 | case CL_INVALID_DEVICE_TYPE: return "Invalid device type"; 225 | case CL_INVALID_PLATFORM: return "Invalid platform"; 226 | case CL_INVALID_DEVICE: return "Invalid device"; 227 | case CL_INVALID_CONTEXT: return "Invalid context"; 228 | case CL_INVALID_QUEUE_PROPERTIES: return "Invalid queue properties"; 229 | case CL_INVALID_COMMAND_QUEUE: return "Invalid command queue"; 230 | case CL_INVALID_HOST_PTR: return "Invalid host pointer"; 231 | case CL_INVALID_MEM_OBJECT: return "Invalid memory object"; 232 | case CL_INVALID_IMAGE_FORMAT_DESCRIPTOR: return "Invalid image format descriptor"; 233 | case CL_INVALID_IMAGE_SIZE: return "Invalid image size"; 234 | case CL_INVALID_SAMPLER: return "Invalid sampler"; 235 | case CL_INVALID_BINARY: return "Invalid binary"; 236 | case CL_INVALID_BUILD_OPTIONS: return "Invalid build options"; 237 | case CL_INVALID_PROGRAM: return "Invalid program"; 238 | case CL_INVALID_PROGRAM_EXECUTABLE: return "Invalid program executable"; 239 | case CL_INVALID_KERNEL_NAME: return "Invalid kernel name"; 240 | case CL_INVALID_KERNEL_DEFINITION: return "Invalid kernel definition"; 241 | case CL_INVALID_KERNEL: return "Invalid kernel"; 242 | case CL_INVALID_ARG_INDEX: return "Invalid argument index"; 243 | case CL_INVALID_ARG_VALUE: return "Invalid argument value"; 244 | case CL_INVALID_ARG_SIZE: return "Invalid argument size"; 245 | case CL_INVALID_KERNEL_ARGS: return "Invalid kernel arguments"; 246 | case CL_INVALID_WORK_DIMENSION: return "Invalid work dimension"; 247 | case CL_INVALID_WORK_GROUP_SIZE: return "Invalid work group size"; 248 | case CL_INVALID_WORK_ITEM_SIZE: return "Invalid work item size"; 249 | case CL_INVALID_GLOBAL_OFFSET: return "Invalid global offset"; 250 | case CL_INVALID_EVENT_WAIT_LIST: return "Invalid event wait list"; 251 | case CL_INVALID_EVENT: return "Invalid event"; 252 | case CL_INVALID_OPERATION: return "Invalid operation"; 253 | case CL_INVALID_GL_OBJECT: return "Invalid OpenGL object"; 254 | case CL_INVALID_BUFFER_SIZE: return "Invalid buffer size"; 255 | case CL_INVALID_MIP_LEVEL: return "Invalid mip-map level"; 256 | default: return "Unknown"; 257 | } 258 | } 259 | 260 | -------------------------------------------------------------------------------- /src/oclutil/openclutilities.h: -------------------------------------------------------------------------------- 1 | /** 2 | * This code is based on Erik Smithad's OpenCLUtilityLibrary 3 | * (https://github.com/smistad/OpenCLUtilityLibrary) with the following license: 4 | * 5 | * Copyright (c) SINTEF, 2014 6 | * All rights reserved. 7 | * 8 | * Redistribution and use in source and binary forms, with or without 9 | * modification, are permitted provided that the following conditions are met: 10 | * 11 | * 1. Redistributions of source code must retain the above copyright notice, this 12 | * list of conditions and the following disclaimer. 13 | * 2. Redistributions in binary form must reproduce the above copyright notice, 14 | * this list of conditions and the following disclaimer in the documentation 15 | * and/or other materials provided with the distribution. 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 OWNER OR CONTRIBUTORS BE LIABLE FOR 21 | * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 26 | * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 | * 28 | * The views and conclusions contained in the software and documentation are those 29 | * of the authors and should not be interpreted as representing official policies, 30 | * either expressed or implied, of SINTEF. 31 | * 32 | */ 33 | 34 | #pragma once 35 | 36 | #define CL_HPP_ENABLE_EXCEPTIONS // cl2.h 37 | //#define __CL_ENABLE_EXCEPTIONS // cl.h 38 | #define CL_USE_DEPRECATED_OPENCL_1_2_APIS false 39 | #define CL_HPP_MINIMUM_OPENCL_VERSION 120 40 | #define CL_HPP_TARGET_OPENCL_VERSION 120 41 | 42 | #pragma GCC diagnostic push 43 | #pragma GCC diagnostic ignored "-Wall" 44 | #if defined(__APPLE__) || defined(__MACOSX) 45 | #include "OpenCL/cl2.hpp" 46 | #else 47 | #include 48 | #endif 49 | #pragma GCC diagnostic pop 50 | 51 | #include 52 | #include 53 | #include 54 | #include 55 | 56 | enum cl_vendor 57 | { 58 | VENDOR_ANY, 59 | VENDOR_NVIDIA, 60 | VENDOR_AMD, 61 | VENDOR_INTEL 62 | }; 63 | 64 | typedef struct OpenCL 65 | { 66 | cl::Context context; 67 | cl::CommandQueue queue; 68 | cl::Program program; 69 | cl::Device device; 70 | cl::Platform platform; 71 | } OpenCL; 72 | 73 | cl::Context createCLContextFromArguments(int argc, char ** argv); 74 | 75 | cl::Context createCLContext(cl_device_type type = CL_DEVICE_TYPE_ALL, cl_vendor vendor = VENDOR_ANY); 76 | cl::Context createCLContext(std::vector devices); 77 | 78 | cl::Platform getPlatform(cl_device_type = CL_DEVICE_TYPE_ALL, cl_vendor vendor = VENDOR_ANY); 79 | 80 | cl::Program buildProgramFromSource(cl::Context context, const std::string &filename, 81 | const std::string &buildOptions = ""); 82 | 83 | std::string getCLErrorString(cl_int err); 84 | -------------------------------------------------------------------------------- /src/qt/colorutils.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * 4 | * \author Mattia Basaglia 5 | * 6 | * \copyright Copyright (C) 2013-2017 Mattia Basaglia 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Lesser General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Lesser General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Lesser General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | #include "src/qt/colorutils.h" 23 | 24 | namespace colorwidgets { 25 | namespace detail { 26 | 27 | QColor color_from_lch(qreal hue, qreal chroma, qreal luma, qreal alpha ) 28 | { 29 | qreal h1 = hue*6; 30 | qreal x = chroma*(1-qAbs(std::fmod(h1,2)-1)); 31 | QColor col; 32 | if ( h1 >= 0 && h1 < 1 ) 33 | col = QColor::fromRgbF(chroma,x,0); 34 | else if ( h1 < 2 ) 35 | col = QColor::fromRgbF(x,chroma,0); 36 | else if ( h1 < 3 ) 37 | col = QColor::fromRgbF(0,chroma,x); 38 | else if ( h1 < 4 ) 39 | col = QColor::fromRgbF(0,x,chroma); 40 | else if ( h1 < 5 ) 41 | col = QColor::fromRgbF(x,0,chroma); 42 | else if ( h1 < 6 ) 43 | col = QColor::fromRgbF(chroma,0,x); 44 | 45 | qreal m = luma - color_lumaF(col); 46 | 47 | return QColor::fromRgbF( 48 | qBound(0.0,col.redF()+m,1.0), 49 | qBound(0.0,col.greenF()+m,1.0), 50 | qBound(0.0,col.blueF()+m,1.0), 51 | alpha); 52 | } 53 | 54 | QColor color_from_hsl(qreal hue, qreal sat, qreal lig, qreal alpha ) 55 | { 56 | qreal chroma = (1 - qAbs(2*lig-1))*sat; 57 | qreal h1 = hue*6; 58 | qreal x = chroma*(1-qAbs(std::fmod(h1,2)-1)); 59 | QColor col; 60 | if ( h1 >= 0 && h1 < 1 ) 61 | col = QColor::fromRgbF(chroma,x,0); 62 | else if ( h1 < 2 ) 63 | col = QColor::fromRgbF(x,chroma,0); 64 | else if ( h1 < 3 ) 65 | col = QColor::fromRgbF(0,chroma,x); 66 | else if ( h1 < 4 ) 67 | col = QColor::fromRgbF(0,x,chroma); 68 | else if ( h1 < 5 ) 69 | col = QColor::fromRgbF(x,0,chroma); 70 | else if ( h1 < 6 ) 71 | col = QColor::fromRgbF(chroma,0,x); 72 | 73 | qreal m = lig-chroma/2; 74 | 75 | return QColor::fromRgbF( 76 | qBound(0.0,col.redF()+m,1.0), 77 | qBound(0.0,col.greenF()+m,1.0), 78 | qBound(0.0,col.blueF()+m,1.0), 79 | alpha); 80 | } 81 | 82 | } // namespace detail 83 | } // namespace colorwidgets 84 | -------------------------------------------------------------------------------- /src/qt/colorutils.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * 4 | * \author Mattia Basaglia 5 | * 6 | * \copyright Copyright (C) 2013-2017 Mattia Basaglia 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Lesser General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Lesser General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Lesser General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | #include 23 | #include 24 | 25 | namespace colorwidgets { 26 | namespace detail { 27 | 28 | 29 | inline qreal color_chromaF(const QColor& c) 30 | { 31 | qreal max = qMax(c.redF(), qMax(c.greenF(), c.blueF())); 32 | qreal min = qMin(c.redF(), qMin(c.greenF(), c.blueF())); 33 | return max - min; 34 | } 35 | 36 | inline qreal color_lumaF(const QColor& c) 37 | { 38 | return 0.30 * c.redF() + 0.59 * c.greenF() + 0.11 * c.blueF(); 39 | } 40 | QColor color_from_lch(qreal hue, qreal chroma, qreal luma, qreal alpha = 1 ); 41 | 42 | inline QColor rainbow_lch(qreal hue) 43 | { 44 | return color_from_lch(hue,1,1); 45 | } 46 | 47 | inline QColor rainbow_hsv(qreal hue) 48 | { 49 | return QColor::fromHsvF(hue,1,1); 50 | } 51 | 52 | inline qreal color_lightnessF(const QColor& c) 53 | { 54 | return ( qMax(c.redF(),qMax(c.greenF(),c.blueF())) + 55 | qMin(c.redF(),qMin(c.greenF(),c.blueF())) ) / 2; 56 | } 57 | 58 | inline qreal color_HSL_saturationF(const QColor& col) 59 | { 60 | qreal c = color_chromaF(col); 61 | qreal l = color_lightnessF(col); 62 | if ( qFuzzyCompare(l+1,1) || qFuzzyCompare(l+1,2) ) 63 | return 0; 64 | return c / (1-qAbs(2*l-1)); 65 | } 66 | 67 | QColor color_from_hsl(qreal hue, qreal sat, qreal lig, qreal alpha = 1 ); 68 | 69 | } // namespace detail 70 | } // namespace colorwidgets 71 | -------------------------------------------------------------------------------- /src/qt/colorwheel.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * 4 | * \author Mattia Basaglia 5 | * 6 | * \copyright Copyright (C) 2013-2017 Mattia Basaglia 7 | * \copyright Copyright (C) 2017 caryoscelus 8 | * 9 | * This program is free software: you can redistribute it and/or modify 10 | * it under the terms of the GNU Lesser General Public License as published by 11 | * the Free Software Foundation, either version 3 of the License, or 12 | * (at your option) any later version. 13 | * 14 | * This program is distributed in the hope that it will be useful, 15 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | * GNU Lesser General Public License for more details. 18 | * 19 | * You should have received a copy of the GNU Lesser General Public License 20 | * along with this program. If not, see . 21 | * 22 | */ 23 | #ifndef COLOR_WHEEL_HPP 24 | #define COLOR_WHEEL_HPP 25 | 26 | #include 27 | 28 | namespace colorwidgets { 29 | 30 | /** 31 | * \brief Display an analog widget that allows the selection of a HSV color 32 | * 33 | * It has an outer wheel to select the Hue and an intenal square to select 34 | * Saturation and Lightness. 35 | */ 36 | class ColorWheel : public QWidget 37 | { 38 | Q_OBJECT 39 | 40 | Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged DESIGNABLE true STORED false ) 41 | Q_PROPERTY(qreal hue READ hue WRITE setHue DESIGNABLE false ) 42 | Q_PROPERTY(qreal saturation READ saturation WRITE setSaturation DESIGNABLE false ) 43 | Q_PROPERTY(qreal value READ value WRITE setValue DESIGNABLE false ) 44 | Q_PROPERTY(unsigned wheelWidth READ wheelWidth WRITE setWheelWidth DESIGNABLE true ) 45 | Q_PROPERTY(DisplayFlags displayFlags READ displayFlags WRITE setDisplayFlags NOTIFY displayFlagsChanged DESIGNABLE true ) 46 | 47 | public: 48 | enum DisplayEnum 49 | { 50 | SHAPE_DEFAULT = 0x000, ///< Use the default shape 51 | SHAPE_TRIANGLE = 0x001, ///< A triangle 52 | SHAPE_SQUARE = 0x002, ///< A square 53 | SHAPE_FLAGS = 0x00f, ///< Mask for the shape flags 54 | 55 | ANGLE_DEFAULT = 0x000, ///< Use the default rotation style 56 | ANGLE_FIXED = 0x010, ///< The inner part doesn't rotate 57 | ANGLE_ROTATING = 0x020, ///< The inner part follows the hue selector 58 | ANGLE_FLAGS = 0x0f0, ///< Mask for the angle flags 59 | 60 | COLOR_DEFAULT = 0x000, ///< Use the default colorspace 61 | COLOR_HSV = 0x100, ///< Use the HSV color space 62 | COLOR_HSL = 0x200, ///< Use the HSL color space 63 | COLOR_LCH = 0x400, ///< Use Luma Chroma Hue (Y_601') 64 | COLOR_FLAGS = 0xf00, ///< Mask for the color space flags 65 | 66 | FLAGS_DEFAULT = 0x000, ///< Use all defaults 67 | FLAGS_ALL = 0xfff ///< Mask matching all flags 68 | }; 69 | Q_DECLARE_FLAGS(DisplayFlags, DisplayEnum) 70 | Q_FLAGS(DisplayFlags) 71 | 72 | explicit ColorWheel(QWidget *parent = 0); 73 | ~ColorWheel(); 74 | 75 | /// Get current color 76 | QColor color() const; 77 | 78 | /// Get all harmony colors (including main) 79 | QList harmonyColors() const; 80 | 81 | /// Get number of harmony colors (including main) 82 | unsigned int harmonyCount() const; 83 | 84 | virtual QSize sizeHint() const Q_DECL_OVERRIDE; 85 | 86 | /// Get current hue in the range [0-1] 87 | qreal hue() const; 88 | 89 | /// Get current saturation in the range [0-1] 90 | qreal saturation() const; 91 | 92 | /// Get current value in the range [0-1] 93 | qreal value() const; 94 | 95 | /// Get the width in pixels of the outer wheel 96 | unsigned int wheelWidth() const; 97 | 98 | /// Set the width in pixels of the outer wheel 99 | void setWheelWidth(unsigned int w); 100 | 101 | /// Get display flags 102 | DisplayFlags displayFlags(DisplayFlags mask = FLAGS_ALL) const; 103 | 104 | /// Set the default display flags 105 | static void setDefaultDisplayFlags(DisplayFlags flags); 106 | 107 | /// Get default display flags 108 | static DisplayFlags defaultDisplayFlags(DisplayFlags mask = FLAGS_ALL); 109 | 110 | /** 111 | * @brief Set a specific display flag 112 | * @param flag Flag replacing the mask 113 | * @param mask Mask to be cleared 114 | */ 115 | void setDisplayFlag(DisplayFlags flag, DisplayFlags mask); 116 | 117 | /// Clear harmony color scheme 118 | void clearHarmonies(); 119 | 120 | /** 121 | * @brief Add harmony color 122 | * @param hue_diff Initial hue difference (in [0-1) range) 123 | * @param editable Whether this harmony should be editable 124 | * @returns Index of newly added harmony 125 | */ 126 | unsigned addHarmony(double hue_diff, bool editable); 127 | 128 | /** 129 | * @brief Add symmetric harmony color 130 | * @param relative_to Index of other harmony that should be symmetric relative to main hue 131 | * @returns Index of newly added harmony 132 | * Editability is inherited from symmetric editor 133 | */ 134 | unsigned addSymmetricHarmony(unsigned relative_to); 135 | 136 | /** 137 | * @brief Add opposite harmony color 138 | * @param relative_to Index of other harmony that should be opposite to this 139 | * @returns Index of newly added harmony 140 | * Editability is inherited from opposite editor 141 | */ 142 | unsigned addOppositeHarmony(unsigned relative_to); 143 | 144 | public Q_SLOTS: 145 | 146 | /// Set current color 147 | void setColor(QColor c); 148 | 149 | /** 150 | * @param h Hue [0-1] 151 | */ 152 | void setHue(qreal h); 153 | 154 | /** 155 | * @param s Saturation [0-1] 156 | */ 157 | void setSaturation(qreal s); 158 | 159 | /** 160 | * @param v Value [0-1] 161 | */ 162 | void setValue(qreal v); 163 | 164 | /** 165 | * @brief Set the display flags 166 | * @param flags which will replace the current ones 167 | */ 168 | void setDisplayFlags(ColorWheel::DisplayFlags flags); 169 | 170 | Q_SIGNALS: 171 | /** 172 | * Emitted when the user selects a color or setColor is called 173 | */ 174 | void colorChanged(QColor); 175 | 176 | /** 177 | * Emitted when the user selects a color 178 | */ 179 | void colorSelected(QColor); 180 | 181 | void displayFlagsChanged(ColorWheel::DisplayFlags flags); 182 | 183 | /** 184 | * Emitted when harmony settings or harmony colors are changed (including due to main hue change) 185 | */ 186 | void harmonyChanged(); 187 | 188 | protected: 189 | void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE; 190 | void mouseMoveEvent(QMouseEvent *) Q_DECL_OVERRIDE; 191 | void mousePressEvent(QMouseEvent *) Q_DECL_OVERRIDE; 192 | void mouseReleaseEvent(QMouseEvent *) Q_DECL_OVERRIDE; 193 | void resizeEvent(QResizeEvent *) Q_DECL_OVERRIDE; 194 | void dragEnterEvent(QDragEnterEvent* event) Q_DECL_OVERRIDE; 195 | void dropEvent(QDropEvent* event) Q_DECL_OVERRIDE; 196 | 197 | private: 198 | class Private; 199 | Private * const p; 200 | }; 201 | 202 | Q_DECLARE_OPERATORS_FOR_FLAGS(ColorWheel::DisplayFlags) 203 | 204 | } // namespace colorwidgets 205 | 206 | #endif // COLOR_WHEEL_HPP 207 | -------------------------------------------------------------------------------- /src/qt/hoverpoints.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2016 The Qt Company Ltd. 4 | ** Contact: https://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the demonstration applications of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see https://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at https://www.qt.io/contact-us. 16 | ** 17 | ** BSD License Usage 18 | ** Alternatively, you may use this file under the terms of the BSD license 19 | ** as follows: 20 | ** 21 | ** "Redistribution and use in source and binary forms, with or without 22 | ** modification, are permitted provided that the following conditions are 23 | ** met: 24 | ** * Redistributions of source code must retain the above copyright 25 | ** notice, this list of conditions and the following disclaimer. 26 | ** * Redistributions in binary form must reproduce the above copyright 27 | ** notice, this list of conditions and the following disclaimer in 28 | ** the documentation and/or other materials provided with the 29 | ** distribution. 30 | ** * Neither the name of The Qt Company Ltd nor the names of its 31 | ** contributors may be used to endorse or promote products derived 32 | ** from this software without specific prior written permission. 33 | ** 34 | ** 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 | ** 47 | ** $QT_END_LICENSE$ 48 | ** 49 | ****************************************************************************/ 50 | 51 | #ifndef HOVERPOINTS_H 52 | #define HOVERPOINTS_H 53 | 54 | #include 55 | 56 | QT_FORWARD_DECLARE_CLASS(QBypassWidget) 57 | 58 | class HoverPoints : public QObject 59 | { 60 | Q_OBJECT 61 | public: 62 | enum PointShape { 63 | CircleShape, 64 | RectangleShape 65 | }; 66 | 67 | enum LockType { 68 | LockToLeft = 0x01, 69 | LockToRight = 0x02, 70 | LockToTop = 0x04, 71 | LockToBottom = 0x08 72 | }; 73 | 74 | enum SortType { 75 | NoSort, 76 | XSort, 77 | YSort 78 | }; 79 | 80 | enum ConnectionType { 81 | NoConnection, 82 | LineConnection, 83 | CurveConnection 84 | }; 85 | 86 | HoverPoints(QWidget *widget, PointShape shape); 87 | 88 | bool eventFilter(QObject *object, QEvent *event) override; 89 | 90 | void paintPoints(); 91 | 92 | inline QRectF boundingRect() const; 93 | void setBoundingRect(const QRectF &boundingRect) { m_bounds = boundingRect; } 94 | 95 | QPolygonF points() const { return m_points; } 96 | QVector colors() const { return m_colors; } 97 | void setPoints(const QPolygonF &points); 98 | void setColoredPoints(const QPolygonF &points, QVector colors); 99 | 100 | QSizeF pointSize() const { return m_pointSize; } 101 | void setPointSize(const QSizeF &size) { m_pointSize = size; } 102 | 103 | SortType sortType() const { return m_sortType; } 104 | void setSortType(SortType sortType) { m_sortType = sortType; } 105 | 106 | ConnectionType connectionType() const { return m_connectionType; } 107 | void setConnectionType(ConnectionType connectionType) { m_connectionType = connectionType; } 108 | 109 | void setConnectionPen(const QPen &pen) { m_connectionPen = pen; } 110 | void setShapePen(const QPen &pen) { m_pointPen = pen; } 111 | void setShapeBrush(const QBrush &brush) { m_pointBrush = brush; } 112 | 113 | void setPointLock(int pos, LockType lock) { m_locks[pos] = lock; } 114 | 115 | void setEditable(bool editable) { m_editable = editable; } 116 | bool editable() const { return m_editable; } 117 | 118 | public slots: 119 | void setEnabled(bool enabled); 120 | void setDisabled(bool disabled) { setEnabled(!disabled); } 121 | 122 | void setColorSelected(const QColor color); 123 | signals: 124 | void pointsChanged(const QPolygonF &points); 125 | void selectionChanged(const QColor &color); 126 | 127 | public: 128 | void firePointChange(); 129 | 130 | private: 131 | inline QRectF pointBoundingRect(int i) const; 132 | void movePoint(int i, const QPointF &newPos, bool emitChange = true); 133 | 134 | QWidget *m_widget; 135 | 136 | QPolygonF m_points; 137 | QVector m_colors; 138 | QRectF m_bounds; 139 | PointShape m_shape; 140 | SortType m_sortType; 141 | ConnectionType m_connectionType; 142 | 143 | QVector m_locks; 144 | 145 | QSizeF m_pointSize; 146 | int m_currentIndex; 147 | bool m_editable; 148 | bool m_enabled; 149 | 150 | QHash m_fingerPointMapping; 151 | 152 | QPen m_pointPen; 153 | QBrush m_pointBrush; 154 | QPen m_connectionPen; 155 | }; 156 | 157 | 158 | inline QRectF HoverPoints::pointBoundingRect(int i) const 159 | { 160 | QPointF p = m_points.at(i); 161 | qreal w = m_pointSize.width(); 162 | qreal h = m_pointSize.height(); 163 | qreal x = p.x() - w / 2; 164 | qreal y = p.y() - h / 2; 165 | return QRectF(x, y, w, h); 166 | } 167 | 168 | inline QRectF HoverPoints::boundingRect() const 169 | { 170 | if (m_bounds.isEmpty()) 171 | return m_widget->rect(); 172 | else 173 | return m_bounds; 174 | } 175 | 176 | #endif // HOVERPOINTS_H 177 | -------------------------------------------------------------------------------- /src/qt/main.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * 4 | * \author Valentin Bruder 5 | * 6 | * \copyright Copyright (C) 2018 Valentin Bruder 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Lesser General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Lesser General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Lesser General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | #include "src/qt/mainwindow.h" 24 | #include 25 | #include 26 | 27 | int main(int argc, char *argv[]) 28 | { 29 | QApplication a(argc, argv); 30 | QErrorMessage::qtHandler(); 31 | 32 | MainWindow w; 33 | w.show(); 34 | 35 | return a.exec(); 36 | } 37 | -------------------------------------------------------------------------------- /src/qt/mainwindow.h: -------------------------------------------------------------------------------- 1 | #ifndef MAINWINDOW_H 2 | #define MAINWINDOW_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | /** 10 | * \file 11 | * 12 | * \author Valentin Bruder 13 | * 14 | * \copyright Copyright (C) 2018 Valentin Bruder 15 | * 16 | * This program is free software: you can redistribute it and/or modify 17 | * it under the terms of the GNU Lesser General Public License as published by 18 | * the Free Software Foundation, either version 3 of the License, or 19 | * (at your option) any later version. 20 | * 21 | * This program is distributed in the hope that it will be useful, 22 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 23 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 24 | * GNU Lesser General Public License for more details. 25 | * 26 | * You should have received a copy of the GNU Lesser General Public License 27 | * along with this program. If not, see . 28 | * 29 | */ 30 | 31 | #pragma once 32 | 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | 43 | namespace Ui { 44 | class MainWindow; 45 | } 46 | 47 | class MainWindow : public QMainWindow 48 | { 49 | Q_OBJECT 50 | 51 | public: 52 | explicit MainWindow(QWidget *parent = 0); 53 | ~MainWindow(); 54 | 55 | protected slots: 56 | void openVolumeFile(); 57 | 58 | void addProgress(); 59 | void finishedLoading(); 60 | 61 | void loadTff(); 62 | void loadRawTff(); 63 | void saveTff(); 64 | void saveRawTff(); 65 | 66 | void loadIndex_and_Sampling_Map(); 67 | 68 | void chooseBackgroundColor(); 69 | void saveCamState(); 70 | void loadCamState(); 71 | void showAboutDialog(); 72 | void updateTransferFunctionFromGradientStops(); 73 | void setLoopTimesteps(); 74 | void nextTimestep(); 75 | void setPlaybackSpeed(int speed); 76 | void playInteractionSequence(); 77 | protected: 78 | void dragEnterEvent(QDragEnterEvent *ev) Q_DECL_OVERRIDE; 79 | void dropEvent(QDropEvent *ev) Q_DECL_OVERRIDE; 80 | void closeEvent(QCloseEvent *event) Q_DECL_OVERRIDE; 81 | void keyPressEvent(QKeyEvent *event) Q_DECL_OVERRIDE; 82 | void showEvent(QShowEvent *event) Q_DECL_OVERRIDE; 83 | 84 | private: 85 | 86 | void setVolumeData(const QString &fileName); 87 | 88 | bool readVolumeFile(const QString &fileName); 89 | 90 | void readTff(const QString &fileName); 91 | 92 | void readSettings(); 93 | void writeSettings(); 94 | 95 | void setStatusText(); 96 | 97 | // ----- Members ----- 98 | Ui::MainWindow *ui; 99 | 100 | QSettings *_settings; 101 | QFutureWatcher *_watcher; 102 | QProgressBar _progBar; 103 | QTimer _timer; 104 | QString _fileName; 105 | QLabel _statusLabel; 106 | QTimer _loopTimer; 107 | }; 108 | 109 | #endif // MAINWINDOW_H 110 | -------------------------------------------------------------------------------- /src/qt/transferfunctionwidget.cpp: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2016 The Qt Company Ltd. 4 | ** Contact: https://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the demonstration applications of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see https://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at https://www.qt.io/contact-us. 16 | ** 17 | ** BSD License Usage 18 | ** Alternatively, you may use this file under the terms of the BSD license 19 | ** as follows: 20 | ** 21 | ** "Redistribution and use in source and binary forms, with or without 22 | ** modification, are permitted provided that the following conditions are 23 | ** met: 24 | ** * Redistributions of source code must retain the above copyright 25 | ** notice, this list of conditions and the following disclaimer. 26 | ** * Redistributions in binary form must reproduce the above copyright 27 | ** notice, this list of conditions and the following disclaimer in 28 | ** the documentation and/or other materials provided with the 29 | ** distribution. 30 | ** * Neither the name of The Qt Company Ltd nor the names of its 31 | ** contributors may be used to endorse or promote products derived 32 | ** from this software without specific prior written permission. 33 | ** 34 | ** 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 | ** 47 | ** $QT_END_LICENSE$ 48 | ** 49 | ****************************************************************************/ 50 | 51 | #include "src/qt/transferfunctionwidget.h" 52 | #include "src/qt/hoverpoints.h" 53 | 54 | #include 55 | 56 | /** 57 | * @brief ShadeWidget::ShadeWidget 58 | * @param type 59 | * @param parent 60 | */ 61 | ShadeWidget::ShadeWidget(ShadeType type, QWidget *parent) 62 | : QWidget(parent) 63 | , _pShadeType(type) 64 | , _pAlphaGradient(QLinearGradient(0, 0, 0, 0)) 65 | { 66 | // Checkers background 67 | if (_pShadeType == ARGBShade) 68 | { 69 | QPixmap pm(20, 20); 70 | QPainter pmp(&pm); 71 | pmp.fillRect(0, 0, 10, 10, Qt::white); 72 | pmp.fillRect(10, 10, 10, 10, Qt::white); 73 | pmp.fillRect(0, 10, 10, 10, Qt::lightGray); 74 | pmp.fillRect(10, 0, 10, 10, Qt::lightGray); 75 | pmp.end(); 76 | QPalette pal = palette(); 77 | pal.setBrush(backgroundRole(), QBrush(pm)); 78 | setAutoFillBackground(true); 79 | setPalette(pal); 80 | } 81 | else 82 | { 83 | setAttribute(Qt::WA_NoBackground); 84 | } 85 | 86 | QPolygonF points; 87 | points << QPointF(0, sizeHint().height()) 88 | << QPointF(sizeHint().width(), 0); 89 | 90 | _pHoverPoints = QSharedPointer(new HoverPoints(this, HoverPoints::CircleShape)); 91 | _pHoverPoints->setConnectionType(HoverPoints::LineConnection); 92 | _pHoverPoints->setPoints(points); 93 | _pHoverPoints->setPointLock(0, HoverPoints::LockToLeft); 94 | _pHoverPoints->setPointLock(1, HoverPoints::LockToRight); 95 | _pHoverPoints->setSortType(HoverPoints::XSort); 96 | 97 | setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred); 98 | 99 | // connect(_pHoverPoints.data(), &HoverPoints::pointsChanged, this, &ShadeWidget::colorsChanged); 100 | connect(_pHoverPoints.data(), &HoverPoints::selectionChanged, 101 | this, &ShadeWidget::selectedPointChanged); 102 | } 103 | 104 | QPolygonF ShadeWidget::points() const 105 | { 106 | return _pHoverPoints->points(); 107 | } 108 | 109 | QVector ShadeWidget::colors() const 110 | { 111 | return _pHoverPoints->colors(); 112 | } 113 | 114 | uint ShadeWidget::colorAt(int x) 115 | { 116 | generateShade(); 117 | 118 | QPolygonF pts = _pHoverPoints->points(); 119 | for (int i=1; i < pts.size(); ++i) 120 | { 121 | if (pts.at(i-1).x() <= x && pts.at(i).x() >= x) 122 | { 123 | QLineF l(pts.at(i-1), pts.at(i)); 124 | l.setLength(l.length() * ((x - l.x1()) / l.dx())); 125 | return _pShade.pixel(qRound(qMin(l.x2(), (qreal(_pShade.width() - 1)))), 126 | qRound(qMin(l.y2(), qreal(_pShade.height() - 1)))); 127 | } 128 | } 129 | return 0; 130 | } 131 | 132 | void ShadeWidget::setGradientStops(const QGradientStops &stops) 133 | { 134 | if (_pShadeType == ARGBShade) 135 | { 136 | _pAlphaGradient = QLinearGradient(0, 0, width(), 0); 137 | for (int i=0; isetSpacing(1); 205 | vbox->setMargin(1); 206 | 207 | _pAlphaShade = new ShadeWidget(ShadeWidget::ARGBShade, this); 208 | _shades.push_back(_pAlphaShade); 209 | 210 | foreach (ShadeWidget *s, _shades) 211 | { 212 | vbox->addWidget(s); 213 | } 214 | 215 | // connect(_pAlphaShade, &ShadeWidget::colorsChanged, this, &TransferFunctionEditor::pointsUpdated); 216 | connect(_pAlphaShade, &ShadeWidget::selectedPointChanged, 217 | this, &TransferFunctionEditor::selectedPointUpdated); 218 | } 219 | 220 | 221 | inline static bool x_less_than(const QPointF &p1, const QPointF &p2) 222 | { 223 | return p1.x() < p2.x(); 224 | } 225 | 226 | void TransferFunctionEditor::resetPoints() 227 | { 228 | QPolygonF pts; 229 | int h_off = this->width() / 10; 230 | int v_off = this->height() / 8; 231 | pts << QPointF(this->width() / 2, this->height() / 2) 232 | << QPointF(this->width() / 2 - h_off, this->height() / 2 - v_off); 233 | 234 | foreach (ShadeWidget *s, _shades) 235 | { 236 | s->hoverPoints()->setPoints(pts); 237 | } 238 | } 239 | 240 | void TransferFunctionEditor::pointsUpdated() 241 | { 242 | qreal w = _pAlphaShade->width(); 243 | qreal h = _pAlphaShade->height(); 244 | QGradientStops stops; 245 | QPolygonF points; 246 | QVector colors; 247 | 248 | foreach (ShadeWidget *s, _shades) 249 | { 250 | colors.append(s->colors()); 251 | points += s->points(); 252 | } 253 | std::sort(points.begin(), points.end(), x_less_than); 254 | 255 | for (int i = 0; i < points.size(); ++i) 256 | { 257 | qreal x = int(points.at(i).x()); 258 | qreal y = int(points.at(i).y()); 259 | if (i + 1 < points.size() && x == points.at(i + 1).x()) 260 | continue; 261 | if (x / w > 1) 262 | return; 263 | 264 | QColor col = colors.at(i); 265 | col.setAlphaF(1.f - y/h); 266 | stops << QGradientStop(x / w, col); 267 | } 268 | 269 | _pAlphaShade->setGradientStops(stops); 270 | _stops = stops; 271 | emit gradientStopsChanged(stops); 272 | } 273 | 274 | 275 | void TransferFunctionEditor::selectedPointUpdated(const QColor color) 276 | { 277 | emit selectedPointChanged(color); 278 | } 279 | 280 | 281 | static void set_shade_points(const QPolygonF &points, ShadeWidget *shade) 282 | { 283 | shade->hoverPoints()->setPoints(points); 284 | shade->hoverPoints()->setPointLock(0, HoverPoints::LockToLeft); 285 | shade->hoverPoints()->setPointLock(points.size() - 1, HoverPoints::LockToRight); 286 | shade->update(); 287 | } 288 | 289 | static void setShadePointsColored(ShadeWidget *shade, const QPolygonF &points, 290 | const QVector &colors) 291 | { 292 | shade->hoverPoints()->setColoredPoints(points, colors); 293 | shade->hoverPoints()->setPointLock(0, HoverPoints::LockToLeft); 294 | shade->hoverPoints()->setPointLock(points.size() - 1, HoverPoints::LockToRight); 295 | shade->update(); 296 | } 297 | 298 | void TransferFunctionEditor::setGradientStops(const QGradientStops &stops) 299 | { 300 | _stops = stops; 301 | QPolygonF points; 302 | QVector colors; 303 | 304 | qreal h_alpha = _pAlphaShade->height(); 305 | 306 | for (int i = 0; i < stops.size(); ++i) 307 | { 308 | qreal pos = stops.at(i).first; 309 | QRgb color = stops.at(i).second.rgba(); 310 | points << QPointF(pos * _pAlphaShade->width(), h_alpha - qAlpha(color) * h_alpha / 255); 311 | colors.push_back(color); 312 | } 313 | 314 | setShadePointsColored(_pAlphaShade, points, colors); 315 | } 316 | 317 | const QGradientStops TransferFunctionEditor::getGradientStops() const 318 | { 319 | return _stops; 320 | } 321 | 322 | 323 | /** 324 | * @brief TransferFunctionEditor::setInterpolation 325 | * @param method 326 | */ 327 | void TransferFunctionEditor::setInterpolation(const QString method) 328 | { 329 | if (method.contains("Quad")) 330 | { 331 | foreach (ShadeWidget *s, _shades) 332 | s->hoverPoints()->setConnectionType(HoverPoints::CurveConnection); 333 | } 334 | else if (method.contains("Linear")) 335 | { 336 | foreach (ShadeWidget *s, _shades) 337 | s->hoverPoints()->setConnectionType(HoverPoints::LineConnection); 338 | } 339 | foreach (ShadeWidget *s, _shades) 340 | s->hoverPoints()->firePointChange(); 341 | 342 | emit pointsUpdated(); 343 | } 344 | 345 | 346 | /** 347 | * @brief TransferFunctionEditor::setColorSelected 348 | * @param color 349 | */ 350 | void TransferFunctionEditor::setColorSelected(const QColor color) 351 | { 352 | // foreach (ShadeWidget *s, _shades) 353 | _pAlphaShade->hoverPoints()->setColorSelected(color); 354 | emit pointsUpdated(); 355 | } 356 | 357 | 358 | //------------------------------------------------------ 359 | /** 360 | * @brief TransferFunctionWidget::TransferFunctionWidget 361 | * @param parent 362 | */ 363 | TransferFunctionWidget::TransferFunctionWidget(QWidget *parent) : QWidget(parent) 364 | { 365 | _pEditor = new TransferFunctionEditor(); 366 | QVBoxLayout *mainLayout = new QVBoxLayout(this); 367 | mainLayout->addWidget(_pEditor); 368 | } 369 | 370 | /** 371 | * @brief TransferFunctionWidget::resetTransferFunction 372 | */ 373 | void TransferFunctionWidget::resetTransferFunction() 374 | { 375 | QGradientStops stops; 376 | stops << QGradientStop(0.00, QColor(0,0,0,0)); 377 | stops << QGradientStop(0.10, QColor(125,125,125,0)); 378 | stops << QGradientStop(1.00, QColor(0,0,0,255)); 379 | _pEditor->setGradientStops(stops); 380 | _pEditor->pointsUpdated(); 381 | } 382 | 383 | 384 | /** 385 | * @brief TransferFunctionWidget::setInterpolation 386 | * @param method 387 | */ 388 | void TransferFunctionWidget::setInterpolation(QString method) 389 | { 390 | _pEditor->setInterpolation(method); 391 | } 392 | 393 | 394 | /** 395 | * @brief TransferFunctionWidget::setColorSelected 396 | * @param color 397 | */ 398 | void TransferFunctionWidget::setColorSelected(const QColor color) 399 | { 400 | _pEditor->setColorSelected(color); 401 | } 402 | 403 | 404 | /** 405 | * @brief TransferFunctionWidget::getEditor 406 | * @return 407 | */ 408 | QPointer TransferFunctionWidget::getEditor() 409 | { 410 | return _pEditor; 411 | } 412 | -------------------------------------------------------------------------------- /src/qt/transferfunctionwidget.h: -------------------------------------------------------------------------------- 1 | /**************************************************************************** 2 | ** 3 | ** Copyright (C) 2016 The Qt Company Ltd. 4 | ** Contact: https://www.qt.io/licensing/ 5 | ** 6 | ** This file is part of the demonstration applications of the Qt Toolkit. 7 | ** 8 | ** $QT_BEGIN_LICENSE:BSD$ 9 | ** Commercial License Usage 10 | ** Licensees holding valid commercial Qt licenses may use this file in 11 | ** accordance with the commercial license agreement provided with the 12 | ** Software or, alternatively, in accordance with the terms contained in 13 | ** a written agreement between you and The Qt Company. For licensing terms 14 | ** and conditions see https://www.qt.io/terms-conditions. For further 15 | ** information use the contact form at https://www.qt.io/contact-us. 16 | ** 17 | ** BSD License Usage 18 | ** Alternatively, you may use this file under the terms of the BSD license 19 | ** as follows: 20 | ** 21 | ** "Redistribution and use in source and binary forms, with or without 22 | ** modification, are permitted provided that the following conditions are 23 | ** met: 24 | ** * Redistributions of source code must retain the above copyright 25 | ** notice, this list of conditions and the following disclaimer. 26 | ** * Redistributions in binary form must reproduce the above copyright 27 | ** notice, this list of conditions and the following disclaimer in 28 | ** the documentation and/or other materials provided with the 29 | ** distribution. 30 | ** * Neither the name of The Qt Company Ltd nor the names of its 31 | ** contributors may be used to endorse or promote products derived 32 | ** from this software without specific prior written permission. 33 | ** 34 | ** 35 | ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 36 | ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 37 | ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 38 | ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 39 | ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 40 | ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 41 | ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 42 | ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 43 | ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 44 | ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 45 | ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." 46 | ** 47 | ** $QT_END_LICENSE$ 48 | ** 49 | ****************************************************************************/ 50 | 51 | #ifndef TRANSFERFUNCTIONWIDGET_H 52 | #define TRANSFERFUNCTIONWIDGET_H 53 | 54 | #include 55 | #include 56 | #include 57 | #include 58 | 59 | class HoverPoints; 60 | 61 | /** 62 | * @brief The ShadeWidget class 63 | */ 64 | class ShadeWidget : public QWidget 65 | { 66 | Q_OBJECT 67 | 68 | public: 69 | enum ShadeType 70 | { 71 | RedShade, 72 | GreenShade, 73 | BlueShade, 74 | ARGBShade 75 | }; 76 | 77 | ShadeWidget(ShadeType type, QWidget *parent); 78 | 79 | void setGradientStops(const QGradientStops &stops); 80 | 81 | void paintEvent(QPaintEvent *e) override; 82 | 83 | QSize sizeHint() const override { return QSize(150, 40); } 84 | QPolygonF points() const; 85 | QVector colors() const; 86 | 87 | QSharedPointer hoverPoints() const { return _pHoverPoints; } 88 | 89 | uint colorAt(int x); 90 | 91 | signals: 92 | void colorsChanged(); 93 | void selectedPointChanged(const QColor color); 94 | 95 | private: 96 | void generateShade(); 97 | 98 | ShadeType _pShadeType; 99 | QImage _pShade; 100 | QSharedPointer _pHoverPoints; 101 | QLinearGradient _pAlphaGradient; 102 | }; 103 | 104 | 105 | /** 106 | * @brief The TransferFunctionEditor class 107 | */ 108 | class TransferFunctionEditor : public QWidget 109 | { 110 | Q_OBJECT 111 | public: 112 | explicit TransferFunctionEditor(QWidget *parent = 0); 113 | 114 | void setGradientStops(const QGradientStops &stops); 115 | 116 | const QGradientStops getGradientStops() const; 117 | 118 | void resetPoints(); 119 | 120 | void setInterpolation(const QString method); 121 | 122 | void setColorSelected(const QColor color); 123 | 124 | public slots: 125 | void pointsUpdated(); 126 | void selectedPointUpdated(const QColor color); 127 | 128 | signals: 129 | void gradientStopsChanged(const QGradientStops &stops); 130 | void selectedPointChanged(const QColor color); 131 | 132 | private: 133 | ShadeWidget *_pAlphaShade; 134 | 135 | QVector _shades; 136 | QGradientStops _stops; 137 | }; 138 | 139 | 140 | /** 141 | * @brief The TransferFunctionWidget class 142 | */ 143 | class TransferFunctionWidget : public QWidget 144 | { 145 | Q_OBJECT 146 | 147 | public: 148 | TransferFunctionWidget(QWidget *parent); 149 | ~TransferFunctionWidget(){} 150 | 151 | QPointer getEditor(); 152 | 153 | public slots: 154 | void resetTransferFunction(); 155 | 156 | void setInterpolation(QString method); 157 | 158 | void setColorSelected(const QColor color); 159 | 160 | private: 161 | QPointer _pEditor; 162 | }; 163 | 164 | #endif // TRANSFERFUNCTIONWIDGET_H 165 | -------------------------------------------------------------------------------- /src/qt/volumerenderwidget.h: -------------------------------------------------------------------------------- 1 | /** 2 | * \file 3 | * 4 | * \author Valentin Bruder 5 | * 6 | * \copyright Copyright (C) 2018 Valentin Bruder 7 | * 8 | * This program is free software: you can redistribute it and/or modify 9 | * it under the terms of the GNU Lesser General Public License as published by 10 | * the Free Software Foundation, either version 3 of the License, or 11 | * (at your option) any later version. 12 | * 13 | * This program is distributed in the hope that it will be useful, 14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | * GNU Lesser General Public License for more details. 17 | * 18 | * You should have received a copy of the GNU Lesser General Public License 19 | * along with this program. If not, see . 20 | * 21 | */ 22 | 23 | #pragma once 24 | 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | #include 42 | #include 43 | #include 44 | #include 45 | #include 46 | #include 47 | 48 | #include "src/core/volumerendercl.h" 49 | 50 | #include 51 | #include 52 | #include 53 | #include 54 | 55 | 56 | struct Benchmark 57 | { 58 | bool active = false; 59 | quint64 iteration = 0; 60 | quint64 gaze_iterations = 100; // gaze iterations per camera state 61 | QString logFileName = ""; 62 | QFile f; 63 | 64 | bool isCameraIteration() 65 | { 66 | // std::cout << this->iteration << " " << (this->iteration % this->gaze_iterations) << std::endl; 67 | return (this->iteration % this->gaze_iterations) == 0; 68 | } 69 | 70 | void writeState(const QString &str) 71 | { 72 | if (!f.isOpen() && !logFileName.isEmpty()) 73 | { 74 | this->f.setFileName(this->logFileName); 75 | bool ok = f.open(QFile::WriteOnly); 76 | if (!ok) 77 | { 78 | qWarning() << "Failed to open benchmark file" << logFileName; 79 | return; 80 | } 81 | } 82 | QTextStream fileStream(&f); 83 | fileStream << str; 84 | } 85 | }; 86 | 87 | class VolumeRenderWidget : public QOpenGLWidget, protected QOpenGLFunctions_4_3_Core 88 | { 89 | Q_OBJECT 90 | 91 | public: 92 | explicit VolumeRenderWidget(QWidget *parent = nullptr); 93 | virtual ~VolumeRenderWidget() override; 94 | 95 | void setupVertexAttribs(); 96 | 97 | void setVolumeData(const QString &fileName); 98 | 99 | void setIndexandSamplingMap(const QString &fileNameIndexMap, 100 | const QString &fileNameSamplingMap, 101 | const QString &fileNameNeighborIndex, 102 | const QString &fileNameNeighborWeights); 103 | 104 | bool hasData() const; 105 | 106 | const QVector4D getVolumeResolution() const; 107 | 108 | void mousePressEvent(QMouseEvent *event) Q_DECL_OVERRIDE; 109 | 110 | void mouseReleaseEvent(QMouseEvent *event) Q_DECL_OVERRIDE; 111 | 112 | void mouseMoveEvent(QMouseEvent *event) Q_DECL_OVERRIDE; 113 | 114 | void wheelEvent(QWheelEvent *event) Q_DECL_OVERRIDE; 115 | 116 | void mouseDoubleClickEvent(QMouseEvent *event) Q_DECL_OVERRIDE; 117 | 118 | void keyReleaseEvent(QKeyEvent *event) Q_DECL_OVERRIDE; 119 | 120 | void updateView(float dx = 0, float dy = 0); 121 | 122 | bool getLoadingFinished() const; 123 | void setLoadingFinished(const bool loadingFinished); 124 | 125 | QVector3D getCamTranslation() const; 126 | void setCamTranslation(const QVector3D &translation); 127 | 128 | QQuaternion getCamRotation() const; 129 | void setCamRotation(const QQuaternion &rotQuat); 130 | 131 | enum RenderingMethod { Standard, LBG_Sampling }; 132 | void setRenderingMethod(int rm); /* sets the current rending method 133 | updates RenderingParameters of the kernel and calls update() to update the screen. */ 134 | 135 | /* 136 | Enables or disables eyetracking and calls update() to update the screen. 137 | Does not change the value of _contRendering. 138 | */ 139 | void setEyetracking(bool eyetracking); 140 | void showSelectEyetrackingDevice(); 141 | #if _WIN32 142 | void actionSelectMonitor(); 143 | static bool MonitorEnumProc(HMONITOR monitor, HDC hdcMnitor, LPRECT rect, LPARAM param); 144 | #endif 145 | 146 | public slots: 147 | void cleanup(); 148 | void resetCam(); 149 | 150 | void updateSamplingRate(double samplingRate); 151 | void updateTransferFunction(QGradientStops stops); 152 | std::vector getRawTransferFunction(QGradientStops stops) const; 153 | void setRawTransferFunction(std::vector tff); 154 | 155 | #undef Bool 156 | void setTffInterpolation(const QString method); 157 | void setCamOrtho(const bool camOrtho); 158 | void setContRendering(const bool setContRendering); 159 | void setIllumination(const int illum); 160 | void setLinearInterpolation(const bool linear); 161 | void setContours(const bool contours); 162 | void setAerial(const bool aerial); 163 | /** 164 | * @brief Set image order empty space skipping. 165 | * @param useEss 166 | */ 167 | void setImgEss(bool useEss); 168 | /** 169 | * @brief Set object order empty space skipping. 170 | * @param useEss 171 | */ 172 | void setObjEss(bool useEss); 173 | void setDrawBox(bool box); 174 | void setBackgroundColor(const QColor col); 175 | void setImageSamplingRate(const double samplingRate); 176 | void setShowOverlay(bool showOverlay); 177 | 178 | void saveFrame(); 179 | void toggleVideoRecording(); 180 | void toggleViewRecording(); 181 | void toggleInteractionLogging(); 182 | void setTimeStep(int timestep); 183 | void setAmbientOcclusion(bool ao); 184 | 185 | void generateLowResVolume(); 186 | 187 | void read(const QJsonObject &json); 188 | void write(QJsonObject &json) const; 189 | 190 | void showSelectOpenCL(); 191 | void reloadKernels(); 192 | void toggleBenchmark(); //QString logFileName = QString(), int gaze_iterations = -1); 193 | 194 | void do_all_Benchmarks(); // do all the benchmarks for some given volumes and transferfunctions 195 | 196 | void playInteractionSequence(const QString &fileName); 197 | signals: 198 | void fpsChanged(double); 199 | void frameSizeChanged(QSize); 200 | void timeSeriesLoaded(int); 201 | 202 | protected: 203 | // Qt specific QOpenGLWidget methods 204 | void initializeGL() Q_DECL_OVERRIDE; 205 | void paintGL() Q_DECL_OVERRIDE; 206 | void resizeGL(const int w, const int h) Q_DECL_OVERRIDE; // generates the two GL textures, GL_TEXTURE0 is active afterwards 207 | 208 | private: 209 | void updateViewMatrix(); 210 | void setGazeFromCursorPos(const QPoint &cursorPos); 211 | void paintOrientationAxis(QPainter &p); 212 | void paintFps(QPainter &p, const double fps, const double lastTime); 213 | double getFps(double offset=0.0); 214 | 215 | // Different methods called from within the paintGL()-method 216 | void paintGL_standard(); 217 | void paintGL_LBG_sampling(); 218 | 219 | // Eyetracking 220 | bool check_eyetracker_availability(bool eyetracking); // Checks if the currently selected eyetracker (_eyetracker) exists 221 | static void gaze_data_callback(TobiiResearchGazeData *gaze_data, void *user_data); 222 | 223 | /** 224 | * @brief Initialize the OpenCL volume renderer. 225 | * @param useGL use OpenGL context sharing 226 | * @param useCPU use CPU as OpenCL device for rendering 227 | */ 228 | void initVolumeRenderer(bool useGL = true, const bool useCPU = false); 229 | 230 | /** 231 | * @brief create the OpenGL output texture to display on the screen quad. 232 | * @param width Width of the texture in pixels. 233 | * @param height Height of the texture in pixels. 234 | */ 235 | void generateOutputTextures(const int width, const int height, GLuint* texture, GLuint tex_unit); 236 | 237 | /** 238 | * @brief Log camera configurations rotation and zoom) to two files selected by the user. 239 | */ 240 | void recordViewConfig() const; 241 | 242 | /** 243 | * @brief Log a user interaction to file. 244 | * @param str String containing the user interaction in the fowllowing format: 245 | * time stamp, interaction type, interaction parameters 246 | */ 247 | void logInteraction(const QString &str) const; 248 | void setSequenceStep(QString line); 249 | 250 | // -------Member variables-------- 251 | // 252 | // OpenGL 253 | QOpenGLVertexArrayObject _screenQuadVao; 254 | QOpenGLShaderProgram _spScreenQuad; 255 | QOpenGLShaderProgram _spOverlaysGL; 256 | QOpenGLBuffer _quadVbo; 257 | 258 | QMatrix4x4 _screenQuadProjMX; 259 | QMatrix4x4 _viewMX; 260 | QMatrix4x4 _modelMX; 261 | QMatrix4x4 _coordViewMX; 262 | QMatrix4x4 _overlayProjMX; 263 | QMatrix4x4 _overlayModelMX; 264 | 265 | QPoint _tffRange; 266 | GLuint _outTexId; // TexId for final output Texture, bound to tex_unit 0 267 | GLuint _tmpTexId; // TexId for temporary Textures like pre interpolation, bound to tex_unit 1 268 | VolumeRenderCL _volumerender; 269 | QEasingCurve _tffInterpol; 270 | int _timestep; 271 | 272 | // Eyetracking 273 | TobiiResearchEyeTracker* _eyetracker; // points to the currently selected eyetracker 274 | TobiiResearchGazeData _gaze_data; // holds the latest collected data from the eyetracking callback 275 | cl_float2 _last_valid_gaze_position; 276 | 277 | // Monitorselection 278 | QPoint _monitor_offset; // The offset which is used for the transformation of the gaze point 279 | int _curr_monitor_width; // The width of the currently selected monitor which is used for the transformation of the gaze point 280 | int _curr_monitor_height; // The height of the currently selected monitor which is used for the transformation of the gaze point 281 | 282 | // global rendering flags 283 | QPoint _lastLocalCursorPos; 284 | QQuaternion _rotQuat; 285 | QVector3D _translation; 286 | 287 | bool _useEyetracking; 288 | bool _noUpdate; 289 | bool _loadingFinished; 290 | bool _writeImage; 291 | bool _recordVideo; 292 | qint64 _imgCount; 293 | QVector _times; 294 | double _imgSamplingRate; // image oversampling rate 295 | bool _useGL; 296 | bool _showOverlay; 297 | bool _logView; 298 | bool _logInteraction; 299 | QString _viewLogFile; 300 | QString _interactionLogFile; 301 | bool _contRendering; 302 | QGradientStops _tffStops; 303 | QElapsedTimer _timer; 304 | RenderingMethod _renderingMethod; // selects the rendering method which will be called within paintGL() 305 | QRandomGenerator64 _prng; 306 | Benchmark _bench; 307 | QStringList _interactionSequence; 308 | int _interactionSequencePos = 0; 309 | bool _playInteraction = false; 310 | }; 311 | --------------------------------------------------------------------------------