diff --git a/HellionChat/Ui/StyleEngine/DrawListExtensions.cs b/HellionChat/Ui/StyleEngine/DrawListExtensions.cs index a92509f..62579c4 100644 --- a/HellionChat/Ui/StyleEngine/DrawListExtensions.cs +++ b/HellionChat/Ui/StyleEngine/DrawListExtensions.cs @@ -120,67 +120,55 @@ internal static class DrawListExtensions dl.AddRectFilledMultiColor(min, max, top, top, bottom, bottom); } - // A vertical tint that does not band. + // A vertical tint that stays under the banding threshold. // - // 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. + // Banding is quantisation: an alpha ramp has as many steps as it has + // distinct values, and across a tall pane each step owns a stripe tens of + // pixels high. The eye then sharpens those edges into scanlines. // - // 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( + // Splitting the ramp into faint layers makes it worse, not better -- each + // layer carries a fraction of the alpha and therefore a fraction of the + // steps, so the stripes get coarser and their blend adds interference. + // + // What actually works is running the whole ramp over a short distance + // instead of a tall one. The same handful of steps then falls within a + // couple of hundred pixels, each stripe is a few pixels tall rather than + // twenty, and they read as a smooth falloff. Below the fade the surface is + // flat, where a constant alpha cannot band at all. + public static void DrawEdgeTint( this ImDrawListPtr dl, Vector2 min, Vector2 max, - uint topAbgr, - uint bottomAbgr, - int layers = 4 + uint edgeAbgr, + float fadeHeight, + bool fromBottom = false ) { - 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); + var fade = MathF.Min(fadeHeight, height); + var clear = edgeAbgr & 0x00FFFFFFu; - for (var i = 0; i < layers; i++) - { - var stretch = band * (i / (float)layers); + if (fromBottom) + dl.AddRectFilledMultiColor( + new Vector2(min.X, max.Y - fade), + max, + clear, + clear, + edgeAbgr, + edgeAbgr + ); + else dl.AddRectFilledMultiColor( min, - new Vector2(max.X, max.Y + stretch), - top, - top, - bottom, - bottom + new Vector2(max.X, min.Y + fade), + edgeAbgr, + edgeAbgr, + clear, + clear ); - } - } - - 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 diff --git a/HellionChat/Ui/StyleEngine/SurfaceBackdrop.cs b/HellionChat/Ui/StyleEngine/SurfaceBackdrop.cs index cd82b0f..ab4c223 100644 --- a/HellionChat/Ui/StyleEngine/SurfaceBackdrop.cs +++ b/HellionChat/Ui/StyleEngine/SurfaceBackdrop.cs @@ -51,13 +51,14 @@ 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)(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); + // Two short fades rather than one tall ramp: light from the top edge, + // shadow gathering at the bottom, flat in between. A ramp stretched over + // the full height is exactly the case that bands, because its handful of + // alpha steps each cover twenty-odd pixels. + var fade = MathF.Min(190f * Metrics.Scale, (max.Y - min.Y) * 0.45f); + dl.DrawEdgeTint(min, max, 0x00FFFFFFu | ((uint)(0x22 * strength * opacity) << 24), fade); + dl.DrawEdgeTint(min, max, (uint)(0x30 * strength * opacity) << 24, fade, fromBottom: true); if (darken > 0f) dl.AddRectFilled(min, max, (uint)(0xFF * darken * opacity) << 24);