1use std::env;
9
10use super::Color;
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq, Default)]
14pub enum ColorMode {
15 #[default]
17 TrueColor,
18 Color256,
20 Basic16,
22}
23
24impl ColorMode {
25 pub fn detect() -> Self {
29 if let Ok(mode) = env::var("PHOTON_COLOR_MODE") {
30 match mode.as_str() {
31 | "truecolor" | "24bit" | "rgb" => return ColorMode::TrueColor,
32 | "256" | "256color" => return ColorMode::Color256,
33 | "16" | "basic" => return ColorMode::Basic16,
34 | _ => {},
35 }
36 }
37 if let Ok(ct) = env::var("COLORTERM") {
38 if ct == "truecolor" || ct == "24bit" {
39 return ColorMode::TrueColor;
40 }
41 }
42 if let Ok(term) = env::var("TERM") {
43 if term.contains("256color") {
44 return ColorMode::Color256;
45 }
46 }
47 ColorMode::TrueColor
48 }
49}
50
51pub fn fg(color: Color, mode: ColorMode) -> String {
53 match mode {
54 | ColorMode::TrueColor => format!("\x1b[38;2;{};{};{}m", color.0, color.1, color.2),
55 | ColorMode::Color256 => format!("\x1b[38;5;{}m", rgb_to_256(color)),
56 | ColorMode::Basic16 => format!("\x1b[{}m", rgb_to_16_fg(color)),
57 }
58}
59
60pub fn bg(color: Color, mode: ColorMode) -> String {
62 match mode {
63 | ColorMode::TrueColor => format!("\x1b[48;2;{};{};{}m", color.0, color.1, color.2),
64 | ColorMode::Color256 => format!("\x1b[48;5;{}m", rgb_to_256(color)),
65 | ColorMode::Basic16 => format!("\x1b[{}m", rgb_to_16_bg(color)),
66 }
67}
68
69pub const RESET: &str = "\x1b[0m";
71
72fn rgb_to_256(color: Color) -> u8 {
75 let Color(r, g, b) = color;
76
77 if r == g && g == b {
79 if r < 8 {
80 return 16;
81 }
82 if r > 248 {
83 return 231;
84 }
85 return 232 + ((r - 8) / 10);
86 }
87
88 let r = closest_cube_level(r);
90 let g = closest_cube_level(g);
91 let b = closest_cube_level(b);
92 16 + 36 * r + 6 * g + b
93}
94
95fn closest_cube_level(v: u8) -> u8 {
96 if v < 48 {
98 0
99 } else if v < 115 {
100 1
101 } else if v < 155 {
102 2
103 } else if v < 195 {
104 3
105 } else if v < 235 {
106 4
107 } else {
108 5
109 }
110}
111
112fn rgb_to_16_fg(color: Color) -> u8 {
115 30 + rgb_to_16_index(color)
116}
117
118fn rgb_to_16_bg(color: Color) -> u8 {
119 40 + rgb_to_16_index(color)
120}
121
122fn rgb_to_16_index(color: Color) -> u8 {
123 let Color(r, g, b) = color;
124 let intensity = (r as u16 + g as u16 + b as u16) / 3;
125
126 let is_bright = intensity > 128;
128 let idx = if r > 128 && g < 128 && b < 128 {
129 1 } else if r < 128 && g > 128 && b < 128 {
131 2 } else if r > 128 && g > 128 && b < 128 {
133 3 } else if r < 128 && g < 128 && b > 128 {
135 4 } else if r > 128 && g < 128 && b > 128 {
137 5 } else if r < 128 && g > 128 && b > 128 {
139 6 } else {
141 0 };
143
144 if is_bright && idx == 0 {
145 7 } else if is_bright {
147 idx + 60 } else {
149 idx
150 }
151}
152
153#[cfg(test)]
154mod tests {
155 use super::*;
156
157 #[test]
158 fn truecolor_fg() {
159 let s = fg(Color::SUNBEAM_ORANGE, ColorMode::TrueColor);
160 assert_eq!(s, "\x1b[38;2;250;82;15m");
161 }
162
163 #[test]
164 fn truecolor_bg() {
165 let s = bg(Color::WARM_IVORY, ColorMode::TrueColor);
166 assert_eq!(s, "\x1b[48;2;255;250;237m");
167 }
168
169 #[test]
170 fn color256_produces_valid_codes() {
171 let s = fg(Color::SUNBEAM_ORANGE, ColorMode::Color256);
172 assert!(s.starts_with("\x1b[38;5;"));
173 assert!(s.ends_with('m'));
174 }
175
176 #[test]
177 fn basic16_produces_valid_codes() {
178 let s = fg(Color::WHITE, ColorMode::Basic16);
179 assert!(s.starts_with("\x1b["));
180 assert!(s.ends_with('m'));
181 }
182}