Skip to content

Instantly share code, notes, and snippets.

@galloscript
Last active July 12, 2024 11:34
Show Gist options
  • Save galloscript/8a5d179e432e062550972afcd1ecf112 to your computer and use it in GitHub Desktop.
Save galloscript/8a5d179e432e062550972afcd1ecf112 to your computer and use it in GitHub Desktop.
Gradient color generator and editor for ImGui
//
// 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;
}
};
//
// 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);
}
@paragon-747
Copy link

Makes sense, thanks for the thought out response!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment