diff --git a/HellionChat/Ui/StyleEngine/DrawListExtensions.cs b/HellionChat/Ui/StyleEngine/DrawListExtensions.cs index cd050a2..a92509f 100644 --- a/HellionChat/Ui/StyleEngine/DrawListExtensions.cs +++ b/HellionChat/Ui/StyleEngine/DrawListExtensions.cs @@ -120,6 +120,69 @@ internal static class DrawListExtensions dl.AddRectFilledMultiColor(min, max, top, top, bottom, bottom); } + // A vertical tint that does not band. + // + // The problem is quantisation, not the ramp: an alpha sweep from 26 to 0 + // only has 26 distinct values, so across 500 pixels each one owns a stripe + // roughly 20px tall -- read as horizontal scanlines. Raising the contrast + // just gives more, thinner stripes. + // + // The fix is to dither. Instead of one ramp, several at a fraction of the + // alpha each, every one offset by a sub-step of the height. Their stripe + // boundaries land at different rows, so where one layer steps up its + // neighbours have not yet, and the blend between them lands between two + // quantised values. Same total tint, layers times the effective resolution, + // for one extra draw call per layer. + public static void DrawDitheredVTint( + this ImDrawListPtr dl, + Vector2 min, + Vector2 max, + uint topAbgr, + uint bottomAbgr, + int layers = 4 + ) + { + if (layers < 1) + layers = 1; + + var height = max.Y - min.Y; + if (height <= 0f) + return; + + // Every layer starts at the top edge and ends slightly lower than the + // last, so they share a starting colour but run at different slopes. + // Shifting them instead would leave a gap at the top where fewer layers + // overlap, which reads as a bright band -- trading one artefact for a + // worse one. + // + // The stagger is a fraction of one quantisation band, so the steps + // interleave rather than landing on top of each other. + var steps = MathF.Max(1f, ((topAbgr >> 24) & 0xFFu) + ((bottomAbgr >> 24) & 0xFFu)); + var band = height / steps; + var share = 1f / layers; + var top = ScaleAlpha(topAbgr, share); + var bottom = ScaleAlpha(bottomAbgr, share); + + for (var i = 0; i < layers; i++) + { + var stretch = band * (i / (float)layers); + dl.AddRectFilledMultiColor( + min, + new Vector2(max.X, max.Y + stretch), + top, + top, + bottom, + bottom + ); + } + } + + private static uint ScaleAlpha(uint abgr, float factor) + { + var a = (uint)Math.Clamp(MathF.Round(((abgr >> 24) & 0xFFu) * factor), 0f, 255f); + return (abgr & 0x00FFFFFFu) | (a << 24); + } + // A rule that fades out along its length instead of stopping dead. A hard // line boxes content in; a fading one suggests a boundary without drawing a // wall, which is the whole difference between a heading and a header bar. diff --git a/HellionChat/Ui/StyleEngine/SurfaceBackdrop.cs b/HellionChat/Ui/StyleEngine/SurfaceBackdrop.cs index c833fae..cd82b0f 100644 --- a/HellionChat/Ui/StyleEngine/SurfaceBackdrop.cs +++ b/HellionChat/Ui/StyleEngine/SurfaceBackdrop.cs @@ -28,7 +28,12 @@ internal sealed class SurfaceBackdrop // Call right after entering a child, before its content. GetWindowDrawList // is then that child's own list, so everything lands behind the content // without a channel split. - internal void Draw(float accentWashHeight = 0.38f, float darken = 0f, float moteIntensity = 1f) + internal void Draw( + float accentWashHeight = 0.38f, + float darken = 0f, + float moteIntensity = 1f, + float strength = 1f + ) { var dl = ImGui.GetWindowDrawList(); var min = ImGui.GetWindowPos(); @@ -46,9 +51,13 @@ internal sealed class SurfaceBackdrop // visible underneath, the ramp only tints it, and the game showing // through breaks up any step that is left. var opacity = ((ImGui.GetColorU32(ImGuiCol.WindowBg) >> 24) & 0xFFu) / 255f; - var lift = 0x00FFFFFFu | ((uint)(0x0E * opacity) << 24); - var drop = 0x00000000u | ((uint)(0x1A * opacity) << 24); - dl.AddRectFilledMultiColor(min, max, lift, lift, drop, drop); + var lift = 0x00FFFFFFu | ((uint)(0x1E * strength * opacity) << 24); + var drop = 0x00000000u | ((uint)(0x34 * strength * opacity) << 24); + + // Dithered, because a plain ramp bands: the alpha sweep has only a few + // dozen distinct values, so each owns a stripe tens of pixels tall, seen + // as horizontal scanlines across the pane. + dl.DrawDitheredVTint(min, max, lift, drop); if (darken > 0f) dl.AddRectFilled(min, max, (uint)(0xFF * darken * opacity) << 24); diff --git a/HellionChat/Ui/Windows/MainWindow.cs b/HellionChat/Ui/Windows/MainWindow.cs index 9ad68c2..b162b93 100644 --- a/HellionChat/Ui/Windows/MainWindow.cs +++ b/HellionChat/Ui/Windows/MainWindow.cs @@ -389,7 +389,7 @@ internal sealed class MainWindow : Window, IFocusableChatWindow // a settings pane, which is read in glances; a chat log is read // line by line, and anything drifting behind the text competes // with it. What is left is barely a texture. - _backdrop.Draw(accentWashHeight: 0f, moteIntensity: 0.10f); + _backdrop.Draw(accentWashHeight: 0f, moteIntensity: 0.10f, strength: 0.45f); if (_activeTab is not null) _messages.Draw(_activeTab);