Skip to content

Commit

Permalink
Widgets: ColorEdit: Fix multiple issues.
Browse files Browse the repository at this point in the history
* Change g.ColorEditLastColor type to ImU32 and store RGB color value.
  - Fixes inability to change hue when saturation is 0. (ocornut#4014)
  - Fixes edgecases where lossy color conversion prevent restoration of hue/saturation.
  - Fixes hue value jitter when modifying color using SV square.
* Fix hue resetting to 0 when it is set to 255 by explicitly restoring hue if it is 0 and previous value was 1.
* Further reduce hue jitter by restoring hue when color is modified using SV square.
  • Loading branch information
rokups committed Apr 12, 2021
1 parent d6a5cc7 commit d578c40
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 27 deletions.
3 changes: 3 additions & 0 deletions docs/CHANGELOG.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ Other Changes:
consistent with the compile-time default. (#3922)
- DragScalar: Add default value for v_speed argument to match higher-level functions. (#3922) [@eliasdaler]
- ColorEdit4: Alpha default to 255 (instead of 0) when omitted in hex input. (#3973) [@squadack]
- ColorEdit4: Fixed not being able to change hue when saturation is 0. (#4014) [@rokups]
- ColorEdit4: Fixed hue resetting to 0 when it is set to 255. [@rokups]
- ColorEdit4: Fixed hue value jitter when source color is stored as rgb in 32bit integer. [@rokups]
- InputText: Do not filter private unicode codepoints (e.g. icons) when pasted from clipboard. (#4005) [@dougbinks]
- LabelText: Fixed clipping of multi-line value text when label is single-line. (#4004)
- LabelText: Fixed vertical alignment of single-line value text when label is multi-line. (#4004)
Expand Down
8 changes: 4 additions & 4 deletions imgui_internal.h
Original file line number Diff line number Diff line change
Expand Up @@ -1481,9 +1481,9 @@ struct ImGuiContext
ImFont InputTextPasswordFont;
ImGuiID TempInputId; // Temporary text input when CTRL+clicking on a slider, etc.
ImGuiColorEditFlags ColorEditOptions; // Store user options for color edit widgets
float ColorEditLastHue; // Backup of last Hue associated to LastColor[3], so we can restore Hue in lossy RGB<>HSV round trips
float ColorEditLastSat; // Backup of last Saturation associated to LastColor[3], so we can restore Saturation in lossy RGB<>HSV round trips
float ColorEditLastColor[3];
float ColorEditLastHue; // Backup of last Hue associated to LastColor, so we can restore Hue in lossy RGB<>HSV round trips
float ColorEditLastSat; // Backup of last Saturation associated to LastColor, so we can restore Saturation in lossy RGB<>HSV round trips
ImU32 ColorEditLastColor; // RGB value with alpha set to 0.
ImVec4 ColorPickerRef; // Initial/reference color at the time of opening the color picker.
float SliderCurrentAccum; // Accumulated slider delta when using navigation controls.
bool SliderCurrentAccumDirty; // Has the accumulated slider delta changed since last time we tried to apply it?
Expand Down Expand Up @@ -1647,7 +1647,7 @@ struct ImGuiContext
TempInputId = 0;
ColorEditOptions = ImGuiColorEditFlags__OptionsDefault;
ColorEditLastHue = ColorEditLastSat = 0.0f;
ColorEditLastColor[0] = ColorEditLastColor[1] = ColorEditLastColor[2] = FLT_MAX;
ColorEditLastColor = 0;
SliderCurrentAccum = 0.0f;
SliderCurrentAccumDirty = false;
DragCurrentAccumDirty = false;
Expand Down
61 changes: 38 additions & 23 deletions imgui_widgets.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4683,6 +4683,35 @@ bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flag
return ColorEdit4(label, col, flags | ImGuiColorEditFlags_NoAlpha);
}

// ColorEdit supports RGB and HSV inputs. In case of RGB input resulting color may have undefined hue and/or saturation.
// Since widget displays both RGB and HSV values we must preserve hue and saturation to prevent these values resetting
// in some cases. Last color pointer is saved along with hue and saturation values. We use color pointer comparison to
// determine whether we should restore hue/saturation. Pointer value is enough to validate source of backed up
// hue/saturation values. Saving and comparing color value is unreliable when source color is stored in int32 as RGB
// since float<->int32 conversions are lossy.
static void RestoreHS(const float* col, float& H, float& S, float& V)
{
ImGuiContext& g = *GImGui;

// This check is optional. Suppose we have two color widgets side by side, both widgets display different colors, but both colors have hue and/or saturation undefined.
// With color check: hue/saturation is preserved in one widget. Editing color in one widget would reset hue/saturation in another one.
// Without color check: common hue/saturation would be displayed in all widgets that have hue/saturation undefined.
// g.ColorEditLastColor was changed to store ImU32 RGB value. This essentially gives us color equality check with reduced precision.
// Tiny external color change would not be detected and this check would still pass. This is OK, since we only restore hue/saturation _only_ if they are undefined,
// therefore this change flipping hue/saturation from undefined to a very tiny value would still be represented in color picker.
if (g.ColorEditLastColor != ImGui::ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0)))
return;

// When S == 0, H is undefined.
// When H == 1 it wraps around to 0.
if (S == 0 || (H == 0 && g.ColorEditLastHue == 1))
H = g.ColorEditLastHue;

// When V == 0, S is undefined.
if (V == 0)
S = g.ColorEditLastSat;
}

// Edit colors components (each component in 0.0f..1.0f range).
// See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
// With typical options: Left-click on color square to open color picker. Right-click to open option menu. CTRL-Click over input fields to edit them and TAB to go to next item.
Expand Down Expand Up @@ -4738,13 +4767,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag
{
// Hue is lost when converting from greyscale rgb (saturation=0). Restore it.
ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
if (memcmp(g.ColorEditLastColor, col, sizeof(float) * 3) == 0)
{
if (f[1] == 0)
f[0] = g.ColorEditLastHue;
if (f[2] == 0)
f[1] = g.ColorEditLastSat;
}
RestoreHS(col, f[0], f[1], f[2]);
}
int i[4] = { IM_F32_TO_INT8_UNBOUND(f[0]), IM_F32_TO_INT8_UNBOUND(f[1]), IM_F32_TO_INT8_UNBOUND(f[2]), IM_F32_TO_INT8_UNBOUND(f[3]) };

Expand Down Expand Up @@ -4877,7 +4900,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag
g.ColorEditLastHue = f[0];
g.ColorEditLastSat = f[1];
ColorConvertHSVtoRGB(f[0], f[1], f[2], f[0], f[1], f[2]);
memcpy(g.ColorEditLastColor, f, sizeof(float) * 3);
g.ColorEditLastColor = ColorConvertFloat4ToU32(ImVec4(f[0], f[1], f[2], 0));
}
if ((flags & ImGuiColorEditFlags_DisplayRGB) && (flags & ImGuiColorEditFlags_InputHSV))
ColorConvertRGBtoHSV(f[0], f[1], f[2], f[0], f[1], f[2]);
Expand Down Expand Up @@ -5012,13 +5035,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl
{
// Hue is lost when converting from greyscale rgb (saturation=0). Restore it.
ColorConvertRGBtoHSV(R, G, B, H, S, V);
if (memcmp(g.ColorEditLastColor, col, sizeof(float) * 3) == 0)
{
if (S == 0)
H = g.ColorEditLastHue;
if (V == 0)
S = g.ColorEditLastSat;
}
RestoreHS(col, H, S, V);
}
else if (flags & ImGuiColorEditFlags_InputHSV)
{
Expand Down Expand Up @@ -5071,6 +5088,10 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl
{
S = ImSaturate((io.MousePos.x - picker_pos.x) / (sv_picker_size - 1));
V = 1.0f - ImSaturate((io.MousePos.y - picker_pos.y) / (sv_picker_size - 1));

// Greatly reduces hue jitter and reset to 0 when hue == 255 and color is rapidly modified using SV square.
if (g.ColorEditLastColor == ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0)))
H = g.ColorEditLastHue;
value_changed = value_changed_sv = true;
}
if (!(flags & ImGuiColorEditFlags_NoOptions))
Expand Down Expand Up @@ -5147,7 +5168,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl
ColorConvertHSVtoRGB(H >= 1.0f ? H - 10 * 1e-6f : H, S > 0.0f ? S : 10 * 1e-6f, V > 0.0f ? V : 1e-6f, col[0], col[1], col[2]);
g.ColorEditLastHue = H;
g.ColorEditLastSat = S;
memcpy(g.ColorEditLastColor, col, sizeof(float) * 3);
g.ColorEditLastColor = ColorConvertFloat4ToU32(ImVec4(col[0], col[1], col[2], 0));
}
else if (flags & ImGuiColorEditFlags_InputHSV)
{
Expand Down Expand Up @@ -5201,13 +5222,7 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl
G = col[1];
B = col[2];
ColorConvertRGBtoHSV(R, G, B, H, S, V);
if (memcmp(g.ColorEditLastColor, col, sizeof(float) * 3) == 0) // Fix local Hue as display below will use it immediately.
{
if (S == 0)
H = g.ColorEditLastHue;
if (V == 0)
S = g.ColorEditLastSat;
}
RestoreHS(col, H, S, V); // Fix local Hue as display below will use it immediately.
}
else if (flags & ImGuiColorEditFlags_InputHSV)
{
Expand Down

0 comments on commit d578c40

Please sign in to comment.