├── .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 | 
16 | 
17 | 
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