Skip to main content

max / makeover

Measure well and bevel fidelity across colour depths The 18-of-31 figure that shaped makeover-tui is an ANSI-16 number and was the only one anyone had. This asks the same questions at 256 and 24 bits. Wells collapse onto their face 18/31 at ANSI-16, 4/31 at 256, 2/31 at truecolor. The residual two are not a terminal problem: they are the themes whose raised surface is already white, so the lightening clamps, and they look the same in a browser. Bevels are worse than recorded and recover better. At least one edge vanishes into its own face on 31/31 themes at ANSI-16, not some of them, which is what the sixteen-colour test was pointing at. At 256 it is 4/31. The two edges never collapse onto each other at any depth.
Author: Max Johnson <me@maxj.phd> · 2026-07-28 22:43 UTC
Signed with PGP, not checked
Commit: f6f5c6773d1dbd8771987cb08b224a31edf19399
Parent: ca7c3f0
1 file changed, +85 insertions, -0 deletions
@@ -1,0 +1,85 @@
1 + //! How often a well survives as a fill, per colour depth.
2 + //!
3 + //! `makeover-tui` degrades depth from colour to structure because two surface
4 + //! intents can quantise onto one palette entry. That is measured at ANSI-16.
5 + //! This asks the same question at 256 and at 24 bits, which is what a modern
6 + //! terminal actually offers.
7 +
8 + fn main() {
9 + let mut n = 0;
10 + let (mut c16, mut c256, mut ctrue) = (0, 0, 0);
11 + let (mut b16, mut b256) = (0, 0);
12 + let (mut e16, mut e256) = (0, 0);
13 +
14 + for (id, src) in makeover::embedded_themes() {
15 + let Ok(table) = src.parse::<toml::Table>() else {
16 + continue;
17 + };
18 + let theme = makeover::ThemeColors {
19 + meta: makeover::parse_meta(id, &table, false),
20 + colors: makeover::extract_colors(&table),
21 + };
22 + let t = makeover::resolve(&theme);
23 + let rgb = |k: &str| t.rgb(k).map(|(r, g, b)| makeover::Rgb { r, g, b });
24 + let (Some(well), Some(raised)) = (rgb("surface-well"), rgb("surface-raised")) else {
25 + continue;
26 + };
27 + let (Some(light), Some(dark)) = (rgb("bevel-light"), rgb("bevel-dark")) else {
28 + continue;
29 + };
30 + n += 1;
31 +
32 + // A fill is invisible when it lands on the same entry as its face.
33 + if makeover::quantize(well, &makeover::ANSI_16)
34 + == makeover::quantize(raised, &makeover::ANSI_16)
35 + {
36 + c16 += 1;
37 + }
38 + if makeover::quantize(well, &makeover::ANSI_256)
39 + == makeover::quantize(raised, &makeover::ANSI_256)
40 + {
41 + c256 += 1;
42 + }
43 + if well.r == raised.r && well.g == raised.g && well.b == raised.b {
44 + ctrue += 1;
45 + }
46 +
47 + // An edge can also vanish into the face it is drawn on, which is the
48 + // other half of the degradation and the more common one.
49 + for (pal, counter) in [
50 + (&makeover::ANSI_16[..], 0usize),
51 + (makeover::ANSI_240, 1usize),
52 + ] {
53 + let face = makeover::quantize(raised, pal);
54 + let lit = makeover::quantize(light, pal);
55 + let shadow = makeover::quantize(dark, pal);
56 + if lit == face || shadow == face {
57 + if counter == 0 { e16 += 1 } else { e256 += 1 }
58 + }
59 + }
60 +
61 + // A bevel loses its second tone the same way.
62 + if makeover::quantize(light, &makeover::ANSI_16)
63 + == makeover::quantize(dark, &makeover::ANSI_16)
64 + {
65 + b16 += 1;
66 + }
67 + if makeover::quantize(light, &makeover::ANSI_256)
68 + == makeover::quantize(dark, &makeover::ANSI_256)
69 + {
70 + b256 += 1;
71 + }
72 + }
73 +
74 + println!("themes measured: {n}\n");
75 + println!("well collapses onto its face:");
76 + println!(" ANSI-16 {c16:>3} / {n}");
77 + println!(" ANSI-256 {c256:>3} / {n}");
78 + println!(" truecolor {ctrue:>3} / {n}");
79 + println!("\nbevel loses its second tone:");
80 + println!(" ANSI-16 {b16:>3} / {n}");
81 + println!(" ANSI-256 {b256:>3} / {n}");
82 + println!("\nat least one bevel edge vanishes into its own face:");
83 + println!(" ANSI-16 {e16:>3} / {n}");
84 + println!(" ANSI-256 {e256:>3} / {n}");
85 + }