fix(chat): the header measured contrast against the wrong colour space

EnsureContrast works in ABGR. I handed it the theme's RGBA on both arguments, so
it swapped red and blue in the foreground and in the background, measured a
contrast between two colours that were never on screen, and returned a result in
the wrong order -- which then went through RgbaToAbgr a second time.

On a violet surface with a teal accent that came out as dark bordeaux on dark
violet: the exact unreadable pairing the call was there to prevent. Reported from
a real screenshot, not from a test, because nothing here is testable without a
draw frame.

Every existing caller in the codebase passes ABGR -- SettingsPalette hands over
_palette.Abgr(...), SegmentedControl uses fields literally named LabelAbgr and
TrackAbgr. Mine were the only three that did not, and all three were written in
this cycle.
This commit is contained in:
2026-08-19 17:09:05 +02:00
parent 80ec7450c8
commit e2b6e7a992
2 changed files with 19 additions and 7 deletions
@@ -286,8 +286,11 @@ internal sealed class LivePreviewPanel : IDisposable
draw.DrawTrackedText(
new Vector2(listOrigin.X + 6f, listOrigin.Y + 4f),
MockChannel,
ColourUtil.RgbaToAbgr(
ColourUtil.EnsureContrast(theme.Colors.Accent, theme.Colors.Surface, 4.5f)
// ABGR in, ABGR out -- see the note in ChannelHeader.
ColourUtil.EnsureContrast(
ColourUtil.RgbaToAbgr(theme.Colors.Accent),
ColourUtil.RgbaToAbgr(theme.Colors.Surface),
4.5f
),
1.8f
);
@@ -69,6 +69,7 @@ internal static class ChannelHeader
var theme = Plugin.Instance.ThemeRegistry.Active;
var surface = theme.Colors.Surface;
var surfaceAbgr = ColourUtil.RgbaToAbgr(surface);
var body = BodyFace(fonts);
var meta = MetaFace(fonts);
@@ -163,7 +164,7 @@ internal static class ChannelHeader
var dl = ImGui.GetWindowDrawList();
var bottomRight = origin + new Vector2(width, height);
dl.AddRectFilled(origin, bottomRight, ColourUtil.RgbaToAbgr(surface));
dl.AddRectFilled(origin, bottomRight, surfaceAbgr);
dl.AddLine(
new Vector2(origin.X, bottomRight.Y - 1f),
new Vector2(bottomRight.X, bottomRight.Y - 1f),
@@ -175,8 +176,14 @@ internal static class ChannelHeader
if (plan.ShowName)
{
var accent = ColourUtil.RgbaToAbgr(
ColourUtil.EnsureContrast(theme.Colors.Accent, surface, 4.5f)
// Converted first, then measured. EnsureContrast works in ABGR --
// handing it the theme's RGBA swaps red and blue on both arguments,
// so it measures a contrast that has nothing to do with what ends up
// on screen and returns a colour in the wrong order on top.
var accent = ColourUtil.EnsureContrast(
ColourUtil.RgbaToAbgr(theme.Colors.Accent),
surfaceAbgr,
4.5f
);
var x = origin.X + inset;
@@ -199,8 +206,10 @@ internal static class ChannelHeader
if (plan.ShowDetail)
{
var muted = ColourUtil.RgbaToAbgr(
ColourUtil.EnsureContrast(theme.Colors.TextMuted, surface, 4.5f)
var muted = ColourUtil.EnsureContrast(
ColourUtil.RgbaToAbgr(theme.Colors.TextMuted),
surfaceAbgr,
4.5f
);
var whereFace = detail.WhereIsTranslated ? body : meta;