├── .clang-format ├── .gitignore ├── CMakeLists.txt ├── CMakeLists_imgui.txt ├── LICENSE ├── README.md ├── docs └── ss.png ├── src ├── ImGradientHDR.cpp └── ImGradientHDR.h └── test └── main.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | IndentWidth: 4 3 | TabWidth: 4 4 | AccessModifierOffset: -4 5 | UseTab: Always 6 | BreakBeforeBraces: Allman 7 | ColumnLimit: 0 8 | 9 | DerivePointerAlignment: false 10 | PointerAlignment: Left 11 | 12 | BinPackArguments: false 13 | BinPackParameters: false 14 | 15 | AllowShortFunctionsOnASingleLine: false 16 | AllowShortIfStatementsOnASingleLine: false 17 | AllowShortCaseLabelsOnASingleLine: false 18 | AllowShortBlocksOnASingleLine: false 19 | BreakConstructorInitializersBeforeComma : true 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | project(ImGradientHDR) 4 | 5 | include(ExternalProject) 6 | 7 | find_package(OpenGL REQUIRED) 8 | find_package(Threads REQUIRED) 9 | 10 | set(imgui_source_path ${CMAKE_CURRENT_BINARY_DIR}/external/imgui/src/external_imgui/) 11 | set(imgui_cmake_file_path ${imgui_source_path}/CMakeLists.txt) 12 | message(${imgui_cmake_file_path}) 13 | 14 | ExternalProject_Add( 15 | external_glfw3 16 | GIT_REPOSITORY https://github.com/glfw/glfw 17 | CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR} 18 | -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} 19 | -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS} 20 | -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} 21 | -DGLFW_BUILD_EXAMPLES=OFF 22 | -DGLFW_BUILD_TESTS=OFF 23 | -DGLFW_BUILD_DOCS=OFF 24 | -DGLFW_INSTALL=ON 25 | ) 26 | 27 | ExternalProject_Add( 28 | external_imgui 29 | DEPENDS external_glfw3 30 | PREFIX ${CMAKE_CURRENT_BINARY_DIR}/external/imgui 31 | DOWNLOAD_DIR ${CMAKE_CURRENT_BINARY_DIR}/external/imgui 32 | URL https://github.com/ocornut/imgui/archive/refs/heads/master.zip 33 | PATCH_COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/CMakeLists_imgui.txt ${imgui_cmake_file_path} 34 | CMAKE_ARGS -DCMAKE_INSTALL_PREFIX=${CMAKE_CURRENT_BINARY_DIR} 35 | -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} 36 | -DCMAKE_C_FLAGS=${CMAKE_C_FLAGS} 37 | -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} 38 | -DGLFW_ROOT=${CMAKE_CURRENT_BINARY_DIR}/include 39 | ) 40 | 41 | add_dependencies(external_imgui 42 | external_glfw3 43 | ) 44 | 45 | add_library(ImGradientHDR 46 | src/ImGradientHDR.cpp 47 | ) 48 | 49 | add_executable(test 50 | test/main.cpp 51 | ) 52 | 53 | target_include_directories(ImGradientHDR PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/src ${OPENGL_INCLUDE_DIR}) 54 | 55 | target_include_directories(test PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/include ${CMAKE_CURRENT_SOURCE_DIR}/src ${OPENGL_INCLUDE_DIR}) 56 | target_link_directories(test PRIVATE ${CMAKE_CURRENT_BINARY_DIR}/lib) 57 | 58 | target_link_libraries(test PRIVATE glfw3 imgui ImGradientHDR Threads::Threads ${OPENGL_LIBRARIES} ${CMAKE_DL_LIBS}) 59 | 60 | if(MSVC) 61 | target_compile_options(ImGradientHDR PRIVATE /W4 /WX /wd"4100") 62 | else() 63 | target_compile_options(ImGradientHDR PRIVATE -Wall -Werror) 64 | endif() 65 | 66 | add_dependencies(test 67 | external_imgui 68 | external_glfw3 69 | ) 70 | -------------------------------------------------------------------------------- /CMakeLists_imgui.txt: -------------------------------------------------------------------------------- 1 | project(imgui) 2 | 3 | cmake_minimum_required(VERSION 3.15) 4 | 5 | option(GLFW_ROOT "GLFW path" "") 6 | 7 | find_package(OpenGL REQUIRED) 8 | 9 | add_library(imgui STATIC imgui.cpp imgui_draw.cpp imgui_tables.cpp imgui_widgets.cpp backends/imgui_impl_opengl3.cpp backends/imgui_impl_glfw.cpp) 10 | 11 | target_include_directories(imgui PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} ${GLFW_ROOT} ${OPENGL_INCLUDE_DIR}) 12 | 13 | install(TARGETS imgui DESTINATION lib) 14 | install(FILES imgui.h imconfig.h backends/imgui_impl_glfw.h backends/imgui_impl_opengl3.h DESTINATION include) 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Effekseer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ImGradientHDR 2 | 3 | ![Gradient](docs/ss.png) 4 | 5 | ## Feature 6 | 7 | Supported parameters 8 | 9 | - Color 10 | - Alpha 11 | - Intensity 12 | 13 | -------------------------------------------------------------------------------- /docs/ss.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/effekseer/ImGradientHDR/cfd2ded4dd4ce6379e935b7f2e9b6ce72689c2d4/docs/ss.png -------------------------------------------------------------------------------- /src/ImGradientHDR.cpp: -------------------------------------------------------------------------------- 1 | #include "ImGradientHDR.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace 10 | { 11 | template 12 | void AddMarker(std::array& a, int32_t& count, T value) 13 | { 14 | const auto lb = std::lower_bound( 15 | a.begin(), 16 | a.begin() + count, 17 | value, 18 | [&](const T& a, const T& b) -> bool 19 | { return a.Position < b.Position; }); 20 | 21 | if (lb != a.end()) 22 | { 23 | const auto ind = lb - a.begin(); 24 | std::copy(a.begin() + ind, a.begin() + count, a.begin() + ind + 1); 25 | *(a.begin() + ind) = value; 26 | count++; 27 | } 28 | } 29 | 30 | enum class DrawMarkerMode 31 | { 32 | Selected, 33 | Unselected, 34 | None, 35 | }; 36 | 37 | void DrawMarker(const ImVec2& pmin, const ImVec2& pmax, const ImU32& color, DrawMarkerMode mode) 38 | { 39 | auto drawList = ImGui::GetWindowDrawList(); 40 | const auto w = static_cast(pmax.x - pmin.x); 41 | const auto h = static_cast(pmax.y - pmin.y); 42 | const auto sign = std::signbit(static_cast(h)) ? -1 : 1; 43 | 44 | const auto margin = 2; 45 | const auto marginh = margin * sign; 46 | 47 | if (mode != DrawMarkerMode::None) 48 | { 49 | const auto outlineColor = mode == DrawMarkerMode::Selected ? ImGui::ColorConvertFloat4ToU32({0.0f, 0.0f, 1.0f, 1.0f}) : ImGui::ColorConvertFloat4ToU32({0.2f, 0.2f, 0.2f, 1.0f}); 50 | 51 | drawList->AddTriangleFilled( 52 | {pmin.x + w / 2, pmin.y}, 53 | {pmin.x + 0, pmin.y + h / 2}, 54 | {pmin.x + w, pmin.y + h / 2}, 55 | outlineColor); 56 | 57 | drawList->AddRectFilled({pmin.x + 0, pmin.y + h / 2}, {pmin.x + w, pmin.y + h}, outlineColor); 58 | } 59 | 60 | drawList->AddTriangleFilled( 61 | {pmin.x + w / 2, pmin.y + marginh}, 62 | {pmin.x + 0 + margin, pmin.y + h / 2}, 63 | {pmin.x + w - margin, pmin.y + h / 2}, 64 | color); 65 | 66 | drawList->AddRectFilled({pmin.x + 0 + margin, pmin.y + h / 2}, {pmin.x + w - margin, pmin.y + h - marginh}, color); 67 | }; 68 | 69 | template 70 | void SortMarkers(std::array& a, int32_t& count, int32_t& selectedIndex, int32_t& draggingIndex) 71 | { 72 | struct SortedMarker 73 | { 74 | int index; 75 | T marker; 76 | }; 77 | 78 | std::vector sortedMarker; 79 | 80 | for (int32_t i = 0; i < count; i++) 81 | { 82 | sortedMarker.emplace_back(SortedMarker{i, a[i]}); 83 | } 84 | 85 | std::sort(sortedMarker.begin(), sortedMarker.end(), [](const SortedMarker& a, const SortedMarker& b) 86 | { return a.marker.Position < b.marker.Position; }); 87 | 88 | for (int32_t i = 0; i < count; i++) 89 | { 90 | a[i] = sortedMarker[i].marker; 91 | } 92 | 93 | if (selectedIndex != -1) 94 | { 95 | for (int32_t i = 0; i < count; i++) 96 | { 97 | if (sortedMarker[i].index == selectedIndex) 98 | { 99 | selectedIndex = i; 100 | break; 101 | } 102 | } 103 | } 104 | 105 | if (draggingIndex != -1) 106 | { 107 | for (int32_t i = 0; i < count; i++) 108 | { 109 | if (sortedMarker[i].index == draggingIndex) 110 | { 111 | draggingIndex = i; 112 | break; 113 | } 114 | } 115 | } 116 | } 117 | 118 | ImU32 GetMarkerColor(const ImGradientHDRState::ColorMarker& marker) 119 | { 120 | const auto c = marker.Color; 121 | return ImGui::ColorConvertFloat4ToU32({c[0], c[1], c[2], 1.0f}); 122 | } 123 | 124 | ImU32 GetMarkerColor(const ImGradientHDRState::AlphaMarker& marker) 125 | { 126 | const auto c = marker.Alpha; 127 | return ImGui::ColorConvertFloat4ToU32({c, c, c, 1.0f}); 128 | } 129 | 130 | enum class MarkerDirection 131 | { 132 | ToUpper, 133 | ToLower, 134 | }; 135 | 136 | struct UpdateMarkerResult 137 | { 138 | bool isChanged; 139 | bool isHovered; 140 | }; 141 | 142 | template 143 | UpdateMarkerResult UpdateMarker( 144 | std::array& markerArray, 145 | int& markerCount, 146 | ImGradientHDRTemporaryState& temporaryState, 147 | ImGradientHDRMarkerType markerType, 148 | const char* keyStr, 149 | ImVec2 originPos, 150 | float width, 151 | float markerWidth, 152 | float markerHeight, 153 | MarkerDirection markerDir) 154 | { 155 | UpdateMarkerResult ret; 156 | ret.isChanged = false; 157 | ret.isHovered = false; 158 | 159 | for (int i = 0; i < markerCount; i++) 160 | { 161 | const auto x = (int)(markerArray[i].Position * width); 162 | ImGui::SetCursorScreenPos({originPos.x + x - 5, originPos.y}); 163 | 164 | DrawMarkerMode mode; 165 | if (temporaryState.selectedMarkerType == markerType && temporaryState.selectedIndex == i) 166 | { 167 | mode = DrawMarkerMode::Selected; 168 | } 169 | else 170 | { 171 | mode = DrawMarkerMode::Unselected; 172 | } 173 | 174 | if (markerDir == MarkerDirection::ToLower) 175 | { 176 | DrawMarker( 177 | {originPos.x + x - 5, originPos.y + markerHeight}, 178 | {originPos.x + x + 5, originPos.y + 0}, 179 | GetMarkerColor(markerArray[i]), 180 | mode); 181 | } 182 | else 183 | { 184 | DrawMarker( 185 | {originPos.x + x - 5, originPos.y + 0}, 186 | {originPos.x + x + 5, originPos.y + markerHeight}, 187 | GetMarkerColor(markerArray[i]), 188 | mode); 189 | } 190 | 191 | ImGui::InvisibleButton((keyStr + std::to_string(i)).c_str(), {markerWidth, markerHeight}); 192 | 193 | ret.isHovered |= ImGui::IsItemHovered(); 194 | 195 | if (temporaryState.draggingIndex == -1 && ImGui::IsItemHovered() && ImGui::IsMouseDown(0)) 196 | { 197 | temporaryState.selectedMarkerType = markerType; 198 | temporaryState.selectedIndex = i; 199 | temporaryState.draggingMarkerType = markerType; 200 | temporaryState.draggingIndex = i; 201 | } 202 | 203 | if (!ImGui::IsMouseDown(0)) 204 | { 205 | temporaryState.draggingIndex = -1; 206 | temporaryState.draggingMarkerType = ImGradientHDRMarkerType::Unknown; 207 | } 208 | 209 | if (temporaryState.draggingMarkerType == markerType && temporaryState.draggingIndex == i && ImGui::IsMouseDragging(0)) 210 | { 211 | const auto diff = ImGui::GetIO().MouseDelta.x / width; 212 | markerArray[i].Position += diff; 213 | markerArray[i].Position = std::max(std::min(markerArray[i].Position, 1.0f), 0.0f); 214 | 215 | ret.isChanged |= diff != 0.0f; 216 | } 217 | } 218 | 219 | return ret; 220 | } 221 | } // namespace 222 | 223 | ImGradientHDRState::ColorMarker* ImGradientHDRState::GetColorMarker(int32_t index) 224 | { 225 | if (index < 0 || 226 | index >= ColorCount) 227 | { 228 | return nullptr; 229 | } 230 | 231 | return &(Colors[index]); 232 | } 233 | 234 | ImGradientHDRState::AlphaMarker* ImGradientHDRState::GetAlphaMarker(int32_t index) 235 | { 236 | if (index < 0 || 237 | index >= AlphaCount) 238 | { 239 | return nullptr; 240 | } 241 | 242 | return &(Alphas[index]); 243 | } 244 | 245 | bool ImGradientHDRState::AddColorMarker(float x, std::array color, float intensity) 246 | { 247 | if (ColorCount >= MarkerMax) 248 | { 249 | return false; 250 | } 251 | 252 | x = std::max(std::min(x, 1.0f), 0.0f); 253 | 254 | const auto marker = ColorMarker{x, color, intensity}; 255 | AddMarker(Colors, ColorCount, marker); 256 | return true; 257 | } 258 | 259 | bool ImGradientHDRState::AddAlphaMarker(float x, float alpha) 260 | { 261 | if (AlphaCount >= MarkerMax) 262 | { 263 | return false; 264 | } 265 | 266 | x = std::max(std::min(x, 1.0f), 0.0f); 267 | 268 | const auto marker = AlphaMarker{x, alpha}; 269 | 270 | AddMarker(Alphas, AlphaCount, marker); 271 | return true; 272 | } 273 | 274 | bool ImGradientHDRState::RemoveColorMarker(int32_t index) 275 | { 276 | if (index >= ColorCount || index < 0) 277 | { 278 | return false; 279 | } 280 | 281 | std::copy(Colors.begin() + index + 1, Colors.end(), Colors.begin() + index); 282 | ColorCount--; 283 | return true; 284 | } 285 | 286 | bool ImGradientHDRState::RemoveAlphaMarker(int32_t index) 287 | { 288 | if (index >= AlphaCount || index < 0) 289 | { 290 | return false; 291 | } 292 | 293 | std::copy(Alphas.begin() + index + 1, Alphas.end(), Alphas.begin() + index); 294 | AlphaCount--; 295 | return true; 296 | } 297 | 298 | std::array ImGradientHDRState::GetCombinedColor(float x) const 299 | { 300 | const auto c = GetColorAndIntensity(x); 301 | return std::array{c[0] * c[3], c[1] * c[3], c[2] * c[3], GetAlpha(x)}; 302 | } 303 | 304 | std::array ImGradientHDRState::GetColorAndIntensity(float x) const 305 | { 306 | if (ColorCount == 0) 307 | { 308 | return std::array{1.0f, 1.0f, 1.0f, 1.0f}; 309 | } 310 | 311 | if (x < Colors[0].Position) 312 | { 313 | const auto c = Colors[0].Color; 314 | return {c[0], c[1], c[2], Colors[0].Intensity}; 315 | } 316 | 317 | if (Colors[ColorCount - 1].Position <= x) 318 | { 319 | const auto c = Colors[ColorCount - 1].Color; 320 | return {c[0], c[1], c[2], Colors[ColorCount - 1].Intensity}; 321 | } 322 | 323 | auto key = ColorMarker(); 324 | key.Position = x; 325 | 326 | auto it = std::lower_bound(Colors.begin(), Colors.begin() + ColorCount, key, [](const ColorMarker& a, const ColorMarker& b) 327 | { return a.Position < b.Position; }); 328 | auto ind = static_cast(std::distance(Colors.begin(), it)); 329 | 330 | { 331 | if (Colors[ind].Position != x) 332 | { 333 | ind--; 334 | } 335 | 336 | if (Colors[ind].Position <= x && x <= Colors[ind + 1].Position) 337 | { 338 | const auto area = Colors[ind + 1].Position - Colors[ind].Position; 339 | if (area == 0) 340 | { 341 | return std::array{Colors[ind].Color[0], Colors[ind].Color[1], Colors[ind].Color[2], Colors[ind].Intensity}; 342 | } 343 | 344 | const auto alpha = (x - Colors[ind].Position) / area; 345 | const auto r = Colors[ind + 1].Color[0] * alpha + Colors[ind].Color[0] * (1.0f - alpha); 346 | const auto g = Colors[ind + 1].Color[1] * alpha + Colors[ind].Color[1] * (1.0f - alpha); 347 | const auto b = Colors[ind + 1].Color[2] * alpha + Colors[ind].Color[2] * (1.0f - alpha); 348 | const auto intensity = Colors[ind + 1].Intensity * alpha + Colors[ind].Intensity * (1.0f - alpha); 349 | return std::array{r, g, b, intensity}; 350 | } 351 | else 352 | { 353 | assert(0); 354 | } 355 | } 356 | 357 | return std::array{1.0f, 1.0f, 1.0f, 1.0f}; 358 | } 359 | 360 | float ImGradientHDRState::GetAlpha(float x) const 361 | { 362 | if (AlphaCount == 0) 363 | { 364 | return 1.0f; 365 | } 366 | 367 | if (x < Alphas[0].Position) 368 | { 369 | return Alphas[0].Alpha; 370 | } 371 | 372 | if (Alphas[AlphaCount - 1].Position <= x) 373 | { 374 | return Alphas[AlphaCount - 1].Alpha; 375 | } 376 | 377 | auto key = AlphaMarker(); 378 | key.Position = x; 379 | 380 | auto it = std::lower_bound(Alphas.begin(), Alphas.begin() + AlphaCount, key, [](const AlphaMarker& a, const AlphaMarker& b) 381 | { return a.Position < b.Position; }); 382 | auto ind = static_cast(std::distance(Alphas.begin(), it)); 383 | 384 | { 385 | if (Alphas[ind].Position != x) 386 | { 387 | ind--; 388 | } 389 | 390 | if (Alphas[ind].Position <= x && x <= Alphas[ind + 1].Position) 391 | { 392 | const auto area = Alphas[ind + 1].Position - Alphas[ind].Position; 393 | if (area == 0) 394 | { 395 | return Alphas[ind].Alpha; 396 | } 397 | 398 | const auto alpha = (x - Alphas[ind].Position) / area; 399 | return Alphas[ind + 1].Alpha * alpha + Alphas[ind].Alpha * (1.0f - alpha); 400 | } 401 | else 402 | { 403 | assert(0); 404 | } 405 | } 406 | 407 | return 1.0f; 408 | } 409 | 410 | bool ImGradientHDR(int32_t gradientID, ImGradientHDRState& state, ImGradientHDRTemporaryState& temporaryState, bool isMarkerShown) 411 | { 412 | bool changed = false; 413 | 414 | ImGui::PushID(gradientID); 415 | 416 | auto originPos = ImGui::GetCursorScreenPos(); 417 | 418 | auto drawList = ImGui::GetWindowDrawList(); 419 | 420 | const auto margin = 5; 421 | 422 | const auto width = ImGui::GetContentRegionAvail().x - margin * 2; 423 | const auto barHeight = 20; 424 | const auto markerWidth = 10; 425 | const auto markerHeight = 15; 426 | 427 | if (isMarkerShown) 428 | { 429 | const auto resultAlpha = UpdateMarker(state.Alphas, state.AlphaCount, temporaryState, ImGradientHDRMarkerType::Alpha, "a", originPos, width, markerWidth, markerHeight, MarkerDirection::ToLower); 430 | 431 | changed |= resultAlpha.isChanged; 432 | 433 | if (temporaryState.draggingMarkerType == ImGradientHDRMarkerType::Alpha) 434 | { 435 | SortMarkers(state.Alphas, state.AlphaCount, temporaryState.selectedIndex, temporaryState.draggingIndex); 436 | } 437 | 438 | ImGui::SetCursorScreenPos(originPos); 439 | 440 | ImGui::InvisibleButton("AlphaArea", {width, static_cast(markerHeight)}); 441 | 442 | if (ImGui::IsItemHovered()) 443 | { 444 | const float x = (ImGui::GetIO().MousePos.x - originPos.x); 445 | const float xn = (ImGui::GetIO().MousePos.x - originPos.x) / width; 446 | const auto c = state.GetAlpha(xn); 447 | 448 | if (!resultAlpha.isHovered && state.AlphaCount < state.Alphas.size()) 449 | { 450 | DrawMarker( 451 | {originPos.x + x - 5, originPos.y + markerHeight}, 452 | {originPos.x + x + 5, originPos.y + 0}, 453 | ImGui::ColorConvertFloat4ToU32({c, c, c, 0.5f}), 454 | DrawMarkerMode::None); 455 | } 456 | 457 | if (ImGui::IsMouseClicked(0)) 458 | { 459 | changed |= state.AddAlphaMarker(xn, c); 460 | } 461 | } 462 | } 463 | 464 | const auto barOriginPos = ImGui::GetCursorScreenPos(); 465 | 466 | ImGui::Dummy({width, static_cast(barHeight)}); 467 | 468 | const int32_t gridSize = 10; 469 | 470 | drawList->AddRectFilled(ImVec2(barOriginPos.x - 2, barOriginPos.y - 2), 471 | ImVec2(barOriginPos.x + width + 2, barOriginPos.y + barHeight + 2), 472 | IM_COL32(100, 100, 100, 255)); 473 | 474 | for (int y = 0; y * gridSize < barHeight; y += 1) 475 | { 476 | for (int x = 0; x * gridSize < width; x += 1) 477 | { 478 | int wgrid = std::min(gridSize, static_cast(width) - x * gridSize); 479 | int hgrid = std::min(gridSize, barHeight - y * gridSize); 480 | ImU32 color = IM_COL32(100, 100, 100, 255); 481 | 482 | if ((x + y) % 2 == 0) 483 | { 484 | color = IM_COL32(50, 50, 50, 255); 485 | } 486 | 487 | drawList->AddRectFilled(ImVec2(barOriginPos.x + x * gridSize, barOriginPos.y + y * gridSize), 488 | ImVec2(barOriginPos.x + x * gridSize + wgrid, barOriginPos.y + y * gridSize + hgrid), 489 | color); 490 | } 491 | } 492 | 493 | { 494 | std::vector xkeys; 495 | xkeys.reserve(16); 496 | 497 | for (int32_t i = 0; i < state.ColorCount; i++) 498 | { 499 | xkeys.emplace_back(state.Colors[i].Position); 500 | } 501 | 502 | for (int32_t i = 0; i < state.AlphaCount; i++) 503 | { 504 | xkeys.emplace_back(state.Alphas[i].Position); 505 | } 506 | 507 | xkeys.emplace_back(0.0f); 508 | xkeys.emplace_back(1.0f); 509 | 510 | auto result = std::unique(xkeys.begin(), xkeys.end()); 511 | xkeys.erase(result, xkeys.end()); 512 | 513 | std::sort(xkeys.begin(), xkeys.end()); 514 | 515 | for (size_t i = 0; i < xkeys.size() - 1; i++) 516 | { 517 | const auto c1 = state.GetCombinedColor(xkeys[i]); 518 | const auto c2 = state.GetCombinedColor(xkeys[i + 1]); 519 | 520 | const auto colorAU32 = ImGui::ColorConvertFloat4ToU32({c1[0], c1[1], c1[2], c1[3]}); 521 | const auto colorBU32 = ImGui::ColorConvertFloat4ToU32({c2[0], c2[1], c2[2], c2[3]}); 522 | 523 | drawList->AddRectFilledMultiColor(ImVec2(barOriginPos.x + xkeys[i] * width, barOriginPos.y), 524 | ImVec2(barOriginPos.x + xkeys[i + 1] * width, barOriginPos.y + barHeight), 525 | colorAU32, 526 | colorBU32, 527 | colorBU32, 528 | colorAU32); 529 | } 530 | } 531 | 532 | if (isMarkerShown) 533 | { 534 | 535 | originPos = ImGui::GetCursorScreenPos(); 536 | 537 | const auto resultColor = UpdateMarker(state.Colors, state.ColorCount, temporaryState, ImGradientHDRMarkerType::Color, "c", originPos, width, markerWidth, markerHeight, MarkerDirection::ToUpper); 538 | 539 | changed |= resultColor.isChanged; 540 | 541 | if (temporaryState.draggingMarkerType == ImGradientHDRMarkerType::Color) 542 | { 543 | SortMarkers(state.Colors, state.ColorCount, temporaryState.selectedIndex, temporaryState.draggingIndex); 544 | } 545 | 546 | ImGui::SetCursorScreenPos(originPos); 547 | 548 | ImGui::InvisibleButton("ColorArea", {width, static_cast(markerHeight)}); 549 | 550 | if (ImGui::IsItemHovered()) 551 | { 552 | const float x = (ImGui::GetIO().MousePos.x - originPos.x); 553 | const float xn = x / width; 554 | const auto c = state.GetColorAndIntensity(xn); 555 | 556 | if (!resultColor.isHovered && state.ColorCount < state.Colors.size()) 557 | { 558 | DrawMarker( 559 | {originPos.x + x - 5, originPos.y + 0}, 560 | {originPos.x + x + 5, originPos.y + markerHeight}, 561 | ImGui::ColorConvertFloat4ToU32({c[0], c[1], c[2], 0.5f}), 562 | DrawMarkerMode::None); 563 | } 564 | 565 | if (ImGui::IsMouseClicked(0)) 566 | { 567 | changed |= state.AddColorMarker(xn, {c[0], c[1], c[2]}, c[3]); 568 | } 569 | } 570 | } 571 | 572 | const auto lastOriginPos = ImGui::GetCursorScreenPos(); 573 | 574 | ImGui::SetCursorScreenPos(barOriginPos); 575 | 576 | ImGui::Dummy({width, static_cast(barHeight)}); 577 | 578 | ImGui::SetCursorScreenPos(lastOriginPos); 579 | 580 | ImGui::PopID(); 581 | 582 | return changed; 583 | } 584 | -------------------------------------------------------------------------------- /src/ImGradientHDR.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | const int32_t MarkerMax = 8; 7 | 8 | struct ImGradientHDRState 9 | { 10 | struct ColorMarker 11 | { 12 | float Position; 13 | std::array Color; 14 | float Intensity; 15 | }; 16 | 17 | struct AlphaMarker 18 | { 19 | float Position; 20 | float Alpha; 21 | }; 22 | 23 | int ColorCount = 0; 24 | int AlphaCount = 0; 25 | std::array Colors; 26 | std::array Alphas; 27 | 28 | ColorMarker* GetColorMarker(int32_t index); 29 | 30 | AlphaMarker* GetAlphaMarker(int32_t index); 31 | 32 | bool AddColorMarker(float x, std::array color, float intensity); 33 | 34 | bool AddAlphaMarker(float x, float alpha); 35 | 36 | bool RemoveColorMarker(int32_t index); 37 | 38 | bool RemoveAlphaMarker(int32_t index); 39 | 40 | std::array GetCombinedColor(float x) const; 41 | 42 | std::array GetColorAndIntensity(float x) const; 43 | 44 | float GetAlpha(float x) const; 45 | }; 46 | 47 | enum class ImGradientHDRMarkerType 48 | { 49 | Color, 50 | Alpha, 51 | Unknown, 52 | }; 53 | 54 | struct ImGradientHDRTemporaryState 55 | { 56 | ImGradientHDRMarkerType selectedMarkerType = ImGradientHDRMarkerType::Unknown; 57 | int selectedIndex = -1; 58 | 59 | ImGradientHDRMarkerType draggingMarkerType = ImGradientHDRMarkerType::Unknown; 60 | int draggingIndex = -1; 61 | }; 62 | 63 | bool ImGradientHDR(int32_t gradientID, ImGradientHDRState& state, ImGradientHDRTemporaryState& temporaryState, bool isMarkerShown = true); 64 | -------------------------------------------------------------------------------- /test/main.cpp: -------------------------------------------------------------------------------- 1 | #include "imgui.h" 2 | #include "imgui_impl_glfw.h" 3 | #include "imgui_impl_opengl3.h" 4 | #include 5 | 6 | #include 7 | 8 | static void glfw_error_callback(int error, const char* description) 9 | { 10 | fprintf(stderr, "Glfw Error %d: %s\n", error, description); 11 | } 12 | 13 | #include 14 | 15 | int main(int, char**) 16 | { 17 | glfwSetErrorCallback(glfw_error_callback); 18 | if (!glfwInit()) 19 | { 20 | return 1; 21 | } 22 | 23 | #if defined(__APPLE__) 24 | // GL 3.2 + GLSL 150 25 | const char* glsl_version = "#version 150"; 26 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); 27 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); 28 | glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only 29 | glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac 30 | #else 31 | // GL 3.0 + GLSL 130 32 | const char* glsl_version = "#version 130"; 33 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); 34 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0); 35 | // glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only 36 | // glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // 3.0+ only 37 | #endif 38 | 39 | GLFWwindow* window = glfwCreateWindow(1280, 720, "ImGradientHDR", NULL, NULL); 40 | if (window == NULL) 41 | { 42 | return 1; 43 | } 44 | glfwMakeContextCurrent(window); 45 | glfwSwapInterval(1); 46 | 47 | IMGUI_CHECKVERSION(); 48 | ImGui::CreateContext(); 49 | ImGuiIO& io = ImGui::GetIO(); 50 | (void)io; 51 | 52 | ImGui::StyleColorsDark(); 53 | 54 | ImGui_ImplGlfw_InitForOpenGL(window, true); 55 | ImGui_ImplOpenGL3_Init(glsl_version); 56 | 57 | ImVec4 clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f); 58 | 59 | int32_t stateID = 10; 60 | 61 | ImGradientHDRState state; 62 | ImGradientHDRTemporaryState tempState; 63 | 64 | state.AddColorMarker(0.0f, {1.0f, 1.0f, 1.0f}, 1.0f); 65 | state.AddColorMarker(1.0f, {1.0f, 1.0f, 1.0f}, 1.0f); 66 | state.AddAlphaMarker(0.0f, 1.0f); 67 | state.AddAlphaMarker(1.0f, 1.0f); 68 | 69 | while (!glfwWindowShouldClose(window)) 70 | { 71 | glfwPollEvents(); 72 | 73 | ImGui_ImplOpenGL3_NewFrame(); 74 | ImGui_ImplGlfw_NewFrame(); 75 | ImGui::NewFrame(); 76 | 77 | { 78 | ImGui::Begin("ImGradientHDR"); 79 | 80 | bool isMarkerShown = true; 81 | ImGradientHDR(stateID, state, tempState, isMarkerShown); 82 | 83 | if (ImGui::IsItemHovered()) 84 | { 85 | ImGui::SetTooltip("Gradient"); 86 | } 87 | 88 | if (tempState.selectedMarkerType == ImGradientHDRMarkerType::Color) 89 | { 90 | auto selectedColorMarker = state.GetColorMarker(tempState.selectedIndex); 91 | if (selectedColorMarker != nullptr) 92 | { 93 | ImGui::ColorEdit3("Color", selectedColorMarker->Color.data(), ImGuiColorEditFlags_Float); 94 | ImGui::DragFloat("Intensity", &selectedColorMarker->Intensity, 0.1f, 0.0f, 100.0f, "%f", 1.0f); 95 | } 96 | } 97 | 98 | if (tempState.selectedMarkerType == ImGradientHDRMarkerType::Alpha) 99 | { 100 | auto selectedAlphaMarker = state.GetAlphaMarker(tempState.selectedIndex); 101 | if (selectedAlphaMarker != nullptr) 102 | { 103 | ImGui::DragFloat("Alpha", &selectedAlphaMarker->Alpha, 0.1f, 0.0f, 1.0f, "%f", 1.0f); 104 | } 105 | } 106 | 107 | if (tempState.selectedMarkerType != ImGradientHDRMarkerType::Unknown) 108 | { 109 | if (ImGui::Button("Delete")) 110 | { 111 | if (tempState.selectedMarkerType == ImGradientHDRMarkerType::Color) 112 | { 113 | state.RemoveColorMarker(tempState.selectedIndex); 114 | tempState = ImGradientHDRTemporaryState{}; 115 | } 116 | else if (tempState.selectedMarkerType == ImGradientHDRMarkerType::Alpha) 117 | { 118 | state.RemoveAlphaMarker(tempState.selectedIndex); 119 | tempState = ImGradientHDRTemporaryState{}; 120 | } 121 | } 122 | } 123 | 124 | ImGui::End(); 125 | } 126 | 127 | ImGui::Render(); 128 | int display_w, display_h; 129 | glfwGetFramebufferSize(window, &display_w, &display_h); 130 | glViewport(0, 0, display_w, display_h); 131 | glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w); 132 | glClear(GL_COLOR_BUFFER_BIT); 133 | ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); 134 | 135 | glfwSwapBuffers(window); 136 | } 137 | 138 | ImGui_ImplOpenGL3_Shutdown(); 139 | ImGui_ImplGlfw_Shutdown(); 140 | ImGui::DestroyContext(); 141 | 142 | glfwDestroyWindow(window); 143 | glfwTerminate(); 144 | 145 | return 0; 146 | } --------------------------------------------------------------------------------