Last active
July 12, 2024 11:34
-
-
Save galloscript/8a5d179e432e062550972afcd1ecf112 to your computer and use it in GitHub Desktop.
Gradient color generator and editor for ImGui
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// imgui_color_gradient.cpp | |
// imgui extension | |
// | |
// Created by David Gallardo on 11/06/16. | |
#include "imgui_color_gradient.h" | |
#include "imgui_internal.h" | |
static const float GRADIENT_BAR_WIDGET_HEIGHT = 25; | |
static const float GRADIENT_BAR_EDITOR_HEIGHT = 40; | |
static const float GRADIENT_MARK_DELETE_DIFFY = 40; | |
ImGradient::ImGradient() | |
{ | |
addMark(0.0f, ImColor(0.0f,0.0f,0.0f)); | |
addMark(1.0f, ImColor(1.0f,1.0f,1.0f)); | |
} | |
ImGradient::~ImGradient() | |
{ | |
for (ImGradientMark* mark : m_marks) | |
{ | |
delete mark; | |
} | |
} | |
void ImGradient::addMark(float position, ImColor const color) | |
{ | |
position = ImClamp(position, 0.0f, 1.0f); | |
ImGradientMark* newMark = new ImGradientMark(); | |
newMark->position = position; | |
newMark->color[0] = color.Value.x; | |
newMark->color[1] = color.Value.y; | |
newMark->color[2] = color.Value.z; | |
m_marks.push_back(newMark); | |
refreshCache(); | |
} | |
void ImGradient::removeMark(ImGradientMark* mark) | |
{ | |
m_marks.remove(mark); | |
refreshCache(); | |
} | |
void ImGradient::getColorAt(float position, float* color) const | |
{ | |
position = ImClamp(position, 0.0f, 1.0f); | |
int cachePos = (position * 255); | |
cachePos *= 3; | |
color[0] = m_cachedValues[cachePos+0]; | |
color[1] = m_cachedValues[cachePos+1]; | |
color[2] = m_cachedValues[cachePos+2]; | |
} | |
void ImGradient::computeColorAt(float position, float* color) const | |
{ | |
position = ImClamp(position, 0.0f, 1.0f); | |
ImGradientMark* lower = nullptr; | |
ImGradientMark* upper = nullptr; | |
for(ImGradientMark* mark : m_marks) | |
{ | |
if(mark->position < position) | |
{ | |
if(!lower || lower->position < mark->position) | |
{ | |
lower = mark; | |
} | |
} | |
if(mark->position >= position) | |
{ | |
if(!upper || upper->position > mark->position) | |
{ | |
upper = mark; | |
} | |
} | |
} | |
if(upper && !lower) | |
{ | |
lower = upper; | |
} | |
else if(!upper && lower) | |
{ | |
upper = lower; | |
} | |
else if(!lower && !upper) | |
{ | |
color[0] = color[1] = color[2] = 0; | |
return; | |
} | |
if(upper == lower) | |
{ | |
color[0] = upper->color[0]; | |
color[1] = upper->color[1]; | |
color[2] = upper->color[2]; | |
} | |
else | |
{ | |
float distance = upper->position - lower->position; | |
float delta = (position - lower->position) / distance; | |
//lerp | |
color[0] = ((1.0f - delta) * lower->color[0]) + ((delta) * upper->color[0]); | |
color[1] = ((1.0f - delta) * lower->color[1]) + ((delta) * upper->color[1]); | |
color[2] = ((1.0f - delta) * lower->color[2]) + ((delta) * upper->color[2]); | |
} | |
} | |
void ImGradient::refreshCache() | |
{ | |
m_marks.sort([](const ImGradientMark * a, const ImGradientMark * b) { return a->position < b->position; }); | |
for(int i = 0; i < 256; ++i) | |
{ | |
computeColorAt(i/255.0f, &m_cachedValues[i*3]); | |
} | |
} | |
namespace ImGui | |
{ | |
static void DrawGradientBar(ImGradient* gradient, | |
struct ImVec2 const & bar_pos, | |
float maxWidth, | |
float height) | |
{ | |
ImVec4 colorA = {1,1,1,1}; | |
ImVec4 colorB = {1,1,1,1}; | |
float prevX = bar_pos.x; | |
float barBottom = bar_pos.y + height; | |
ImGradientMark* prevMark = nullptr; | |
ImDrawList* draw_list = ImGui::GetWindowDrawList(); | |
draw_list->AddRectFilled(ImVec2(bar_pos.x - 2, bar_pos.y - 2), | |
ImVec2(bar_pos.x + maxWidth + 2, barBottom + 2), | |
IM_COL32(100, 100, 100, 255)); | |
if(gradient->getMarks().size() == 0) | |
{ | |
draw_list->AddRectFilled(ImVec2(bar_pos.x, bar_pos.y), | |
ImVec2(bar_pos.x + maxWidth, barBottom), | |
IM_COL32(255, 255, 255, 255)); | |
} | |
ImU32 colorAU32 = 0; | |
ImU32 colorBU32 = 0; | |
for(auto markIt = gradient->getMarks().begin(); markIt != gradient->getMarks().end(); ++markIt ) | |
{ | |
ImGradientMark* mark = *markIt; | |
float from = prevX; | |
float to = prevX = bar_pos.x + mark->position * maxWidth; | |
if(prevMark == nullptr) | |
{ | |
colorA.x = mark->color[0]; | |
colorA.y = mark->color[1]; | |
colorA.z = mark->color[2]; | |
} | |
else | |
{ | |
colorA.x = prevMark->color[0]; | |
colorA.y = prevMark->color[1]; | |
colorA.z = prevMark->color[2]; | |
} | |
colorB.x = mark->color[0]; | |
colorB.y = mark->color[1]; | |
colorB.z = mark->color[2]; | |
colorAU32 = ImGui::ColorConvertFloat4ToU32(colorA); | |
colorBU32 = ImGui::ColorConvertFloat4ToU32(colorB); | |
if(mark->position > 0.0) | |
{ | |
draw_list->AddRectFilledMultiColor(ImVec2(from, bar_pos.y), | |
ImVec2(to, barBottom), | |
colorAU32, colorBU32, colorBU32, colorAU32); | |
} | |
prevMark = mark; | |
} | |
if(prevMark && prevMark->position < 1.0) | |
{ | |
draw_list->AddRectFilledMultiColor(ImVec2(prevX, bar_pos.y), | |
ImVec2(bar_pos.x + maxWidth, barBottom), | |
colorBU32, colorBU32, colorBU32, colorBU32); | |
} | |
ImGui::SetCursorScreenPos(ImVec2(bar_pos.x, bar_pos.y + height + 10.0f)); | |
} | |
static void DrawGradientMarks(ImGradient* gradient, | |
ImGradientMark* & draggingMark, | |
ImGradientMark* & selectedMark, | |
struct ImVec2 const & bar_pos, | |
float maxWidth, | |
float height) | |
{ | |
ImVec4 colorA = {1,1,1,1}; | |
ImVec4 colorB = {1,1,1,1}; | |
float barBottom = bar_pos.y + height; | |
ImGradientMark* prevMark = nullptr; | |
ImDrawList* draw_list = ImGui::GetWindowDrawList(); | |
ImU32 colorAU32 = 0; | |
ImU32 colorBU32 = 0; | |
for(auto markIt = gradient->getMarks().begin(); markIt != gradient->getMarks().end(); ++markIt ) | |
{ | |
ImGradientMark* mark = *markIt; | |
if(!selectedMark) | |
{ | |
selectedMark = mark; | |
} | |
float to = bar_pos.x + mark->position * maxWidth; | |
if(prevMark == nullptr) | |
{ | |
colorA.x = mark->color[0]; | |
colorA.y = mark->color[1]; | |
colorA.z = mark->color[2]; | |
} | |
else | |
{ | |
colorA.x = prevMark->color[0]; | |
colorA.y = prevMark->color[1]; | |
colorA.z = prevMark->color[2]; | |
} | |
colorB.x = mark->color[0]; | |
colorB.y = mark->color[1]; | |
colorB.z = mark->color[2]; | |
colorAU32 = ImGui::ColorConvertFloat4ToU32(colorA); | |
colorBU32 = ImGui::ColorConvertFloat4ToU32(colorB); | |
draw_list->AddTriangleFilled(ImVec2(to, bar_pos.y + (height - 6)), | |
ImVec2(to - 6, barBottom), | |
ImVec2(to + 6, barBottom), IM_COL32(100, 100, 100, 255)); | |
draw_list->AddRectFilled(ImVec2(to - 6, barBottom), | |
ImVec2(to + 6, bar_pos.y + (height + 12)), | |
IM_COL32(100, 100, 100, 255), 1.0f, 1.0f); | |
draw_list->AddRectFilled(ImVec2(to - 5, bar_pos.y + (height + 1)), | |
ImVec2(to + 5, bar_pos.y + (height + 11)), | |
IM_COL32(0, 0, 0, 255), 1.0f, 1.0f); | |
if(selectedMark == mark) | |
{ | |
draw_list->AddTriangleFilled(ImVec2(to, bar_pos.y + (height - 3)), | |
ImVec2(to - 4, barBottom + 1), | |
ImVec2(to + 4, barBottom + 1), IM_COL32(0, 255, 0, 255)); | |
draw_list->AddRect(ImVec2(to - 5, bar_pos.y + (height + 1)), | |
ImVec2(to + 5, bar_pos.y + (height + 11)), | |
IM_COL32(0, 255, 0, 255), 1.0f, 1.0f); | |
} | |
draw_list->AddRectFilledMultiColor(ImVec2(to - 3, bar_pos.y + (height + 3)), | |
ImVec2(to + 3, bar_pos.y + (height + 9)), | |
colorBU32, colorBU32, colorBU32, colorBU32); | |
ImGui::SetCursorScreenPos(ImVec2(to - 6, barBottom)); | |
ImGui::InvisibleButton("mark", ImVec2(12, 12)); | |
if(ImGui::IsItemHovered()) | |
{ | |
if(ImGui::IsMouseClicked(0)) | |
{ | |
selectedMark = mark; | |
draggingMark = mark; | |
} | |
} | |
prevMark = mark; | |
} | |
ImGui::SetCursorScreenPos(ImVec2(bar_pos.x, bar_pos.y + height + 20.0f)); | |
} | |
bool GradientButton(ImGradient* gradient) | |
{ | |
if(!gradient) return false; | |
ImVec2 widget_pos = ImGui::GetCursorScreenPos(); | |
// ImDrawList* draw_list = ImGui::GetWindowDrawList(); | |
float maxWidth = ImMax(250.0f, ImGui::GetContentRegionAvailWidth() - 100.0f); | |
bool clicked = ImGui::InvisibleButton("gradient_bar", ImVec2(maxWidth, GRADIENT_BAR_WIDGET_HEIGHT)); | |
DrawGradientBar(gradient, widget_pos, maxWidth, GRADIENT_BAR_WIDGET_HEIGHT); | |
return clicked; | |
} | |
bool GradientEditor(ImGradient* gradient, | |
ImGradientMark* & draggingMark, | |
ImGradientMark* & selectedMark) | |
{ | |
if(!gradient) return false; | |
bool modified = false; | |
ImVec2 bar_pos = ImGui::GetCursorScreenPos(); | |
bar_pos.x += 10; | |
float maxWidth = ImGui::GetContentRegionAvailWidth() - 20; | |
float barBottom = bar_pos.y + GRADIENT_BAR_EDITOR_HEIGHT; | |
ImGui::InvisibleButton("gradient_editor_bar", ImVec2(maxWidth, GRADIENT_BAR_EDITOR_HEIGHT)); | |
if(ImGui::IsItemHovered() && ImGui::IsMouseClicked(0)) | |
{ | |
float pos = (ImGui::GetIO().MousePos.x - bar_pos.x) / maxWidth; | |
float newMarkCol[4]; | |
gradient->getColorAt(pos, newMarkCol); | |
gradient->addMark(pos, ImColor(newMarkCol[0], newMarkCol[1], newMarkCol[2])); | |
} | |
DrawGradientBar(gradient, bar_pos, maxWidth, GRADIENT_BAR_EDITOR_HEIGHT); | |
DrawGradientMarks(gradient, draggingMark, selectedMark, bar_pos, maxWidth, GRADIENT_BAR_EDITOR_HEIGHT); | |
if(!ImGui::IsMouseDown(0) && draggingMark) | |
{ | |
draggingMark = nullptr; | |
} | |
if(ImGui::IsMouseDragging(0) && draggingMark) | |
{ | |
float increment = ImGui::GetIO().MouseDelta.x / maxWidth; | |
bool insideZone = (ImGui::GetIO().MousePos.x > bar_pos.x) && | |
(ImGui::GetIO().MousePos.x < bar_pos.x + maxWidth); | |
if(increment != 0.0f && insideZone) | |
{ | |
draggingMark->position += increment; | |
draggingMark->position = ImClamp(draggingMark->position, 0.0f, 1.0f); | |
gradient->refreshCache(); | |
modified = true; | |
} | |
float diffY = ImGui::GetIO().MousePos.y - barBottom; | |
if(diffY >= GRADIENT_MARK_DELETE_DIFFY) | |
{ | |
gradient->removeMark(draggingMark); | |
draggingMark = nullptr; | |
selectedMark = nullptr; | |
modified = true; | |
} | |
} | |
if(!selectedMark && gradient->getMarks().size() > 0) | |
{ | |
selectedMark = gradient->getMarks().front(); | |
} | |
if(selectedMark) | |
{ | |
bool colorModified = ImGui::ColorPicker3(selectedMark->color); | |
if(selectedMark && colorModified) | |
{ | |
modified = true; | |
gradient->refreshCache(); | |
} | |
} | |
return modified; | |
} | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// imgui_color_gradient.h | |
// imgui extension | |
// | |
// Created by David Gallardo on 11/06/16. | |
/* | |
Usage: | |
::GRADIENT DATA:: | |
ImGradient gradient; | |
::BUTTON:: | |
if(ImGui::GradientButton(&gradient)) | |
{ | |
//set show editor flag to true/false | |
} | |
::EDITOR:: | |
static ImGradientMark* draggingMark = nullptr; | |
static ImGradientMark* selectedMark = nullptr; | |
bool updated = ImGui::GradientEditor(&gradient, draggingMark, selectedMark); | |
::GET A COLOR:: | |
float color[3]; | |
gradient.getColorAt(0.3f, color); //position from 0 to 1 | |
::MODIFY GRADIENT WITH CODE:: | |
gradient.getMarks().clear(); | |
gradient.addMark(0.0f, ImColor(0.2f, 0.1f, 0.0f)); | |
gradient.addMark(0.7f, ImColor(120, 200, 255)); | |
::WOOD BROWNS PRESET:: | |
gradient.getMarks().clear(); | |
gradient.addMark(0.0f, ImColor(0xA0, 0x79, 0x3D)); | |
gradient.addMark(0.2f, ImColor(0xAA, 0x83, 0x47)); | |
gradient.addMark(0.3f, ImColor(0xB4, 0x8D, 0x51)); | |
gradient.addMark(0.4f, ImColor(0xBE, 0x97, 0x5B)); | |
gradient.addMark(0.6f, ImColor(0xC8, 0xA1, 0x65)); | |
gradient.addMark(0.7f, ImColor(0xD2, 0xAB, 0x6F)); | |
gradient.addMark(0.8f, ImColor(0xDC, 0xB5, 0x79)); | |
gradient.addMark(1.0f, ImColor(0xE6, 0xBF, 0x83)); | |
*/ | |
#pragma once | |
#include "imgui.h" | |
#include <list> | |
struct ImGradientMark | |
{ | |
float color[4]; | |
float position; //0 to 1 | |
}; | |
class ImGradient | |
{ | |
public: | |
ImGradient(); | |
~ImGradient(); | |
void getColorAt(float position, float* color) const; | |
void addMark(float position, ImColor const color); | |
void removeMark(ImGradientMark* mark); | |
void refreshCache(); | |
std::list<ImGradientMark*> & getMarks(){ return m_marks; } | |
private: | |
void computeColorAt(float position, float* color) const; | |
std::list<ImGradientMark*> m_marks; | |
float m_cachedValues[256 * 3]; | |
}; | |
namespace ImGui | |
{ | |
bool GradientButton(ImGradient* gradient); | |
bool GradientEditor(ImGradient* gradient, | |
ImGradientMark* & draggingMark, | |
ImGradientMark* & selectedMark); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Makes sense, thanks for the thought out response!