1package colorprofile
2
3import (
4 "image/color"
5 "math"
6
7 "github.com/charmbracelet/x/ansi"
8 "github.com/lucasb-eyer/go-colorful"
9)
10
11// Profile is a color profile: NoTTY, Ascii, ANSI, ANSI256, or TrueColor.
12type Profile byte
13
14const (
15 // NoTTY is a profile with no terminal support.
16 NoTTY Profile = iota
17 // Ascii is a profile with no color support.
18 Ascii //nolint:revive
19 // ANSI is a profile with 16 colors (4-bit).
20 ANSI
21 // ANSI256 is a profile with 256 colors (8-bit).
22 ANSI256
23 // TrueColor is a profile with 16 million colors (24-bit).
24 TrueColor
25)
26
27// String returns the string representation of a Profile.
28func (p Profile) String() string {
29 switch p {
30 case TrueColor:
31 return "TrueColor"
32 case ANSI256:
33 return "ANSI256"
34 case ANSI:
35 return "ANSI"
36 case Ascii:
37 return "Ascii"
38 case NoTTY:
39 return "NoTTY"
40 }
41 return "Unknown"
42}
43
44// Convert transforms a given Color to a Color supported within the Profile.
45func (p Profile) Convert(c color.Color) color.Color {
46 if p <= Ascii {
47 return nil
48 }
49
50 switch c := c.(type) {
51 case ansi.BasicColor:
52 return c
53
54 case ansi.ExtendedColor:
55 if p == ANSI {
56 return ansi256ToANSIColor(c)
57 }
58 return c
59
60 case ansi.TrueColor, color.Color:
61 h, ok := colorful.MakeColor(c)
62 if !ok {
63 return nil
64 }
65 if p != TrueColor {
66 ac := hexToANSI256Color(h)
67 if p == ANSI {
68 return ansi256ToANSIColor(ac)
69 }
70 return ac
71 }
72 return c
73 }
74
75 return c
76}
77
78func hexToANSI256Color(c colorful.Color) ansi.ExtendedColor {
79 v2ci := func(v float64) int {
80 if v < 48 {
81 return 0
82 }
83 if v < 115 {
84 return 1
85 }
86 return int((v - 35) / 40)
87 }
88
89 // Calculate the nearest 0-based color index at 16..231
90 r := v2ci(c.R * 255.0) // 0..5 each
91 g := v2ci(c.G * 255.0)
92 b := v2ci(c.B * 255.0)
93 ci := 36*r + 6*g + b /* 0..215 */
94
95 // Calculate the represented colors back from the index
96 i2cv := [6]int{0, 0x5f, 0x87, 0xaf, 0xd7, 0xff}
97 cr := i2cv[r] // r/g/b, 0..255 each
98 cg := i2cv[g]
99 cb := i2cv[b]
100
101 // Calculate the nearest 0-based gray index at 232..255
102 var grayIdx int
103 average := (cr + cg + cb) / 3
104 if average > 238 {
105 grayIdx = 23
106 } else {
107 grayIdx = (average - 3) / 10 // 0..23
108 }
109 gv := 8 + 10*grayIdx // same value for r/g/b, 0..255
110
111 // Return the one which is nearer to the original input rgb value
112 c2 := colorful.Color{R: float64(cr) / 255.0, G: float64(cg) / 255.0, B: float64(cb) / 255.0}
113 g2 := colorful.Color{R: float64(gv) / 255.0, G: float64(gv) / 255.0, B: float64(gv) / 255.0}
114 colorDist := c.DistanceHSLuv(c2)
115 grayDist := c.DistanceHSLuv(g2)
116
117 if colorDist <= grayDist {
118 return ansi.ExtendedColor(16 + ci) //nolint:gosec
119 }
120 return ansi.ExtendedColor(232 + grayIdx) //nolint:gosec
121}
122
123func ansi256ToANSIColor(c ansi.ExtendedColor) ansi.BasicColor {
124 var r int
125 md := math.MaxFloat64
126
127 h, _ := colorful.Hex(ansiHex[c])
128 for i := 0; i <= 15; i++ {
129 hb, _ := colorful.Hex(ansiHex[i])
130 d := h.DistanceHSLuv(hb)
131
132 if d < md {
133 md = d
134 r = i
135 }
136 }
137
138 return ansi.BasicColor(r) //nolint:gosec
139}
140
141// RGB values of ANSI colors (0-255).
142var ansiHex = []string{
143 "#000000",
144 "#800000",
145 "#008000",
146 "#808000",
147 "#000080",
148 "#800080",
149 "#008080",
150 "#c0c0c0",
151 "#808080",
152 "#ff0000",
153 "#00ff00",
154 "#ffff00",
155 "#0000ff",
156 "#ff00ff",
157 "#00ffff",
158 "#ffffff",
159 "#000000",
160 "#00005f",
161 "#000087",
162 "#0000af",
163 "#0000d7",
164 "#0000ff",
165 "#005f00",
166 "#005f5f",
167 "#005f87",
168 "#005faf",
169 "#005fd7",
170 "#005fff",
171 "#008700",
172 "#00875f",
173 "#008787",
174 "#0087af",
175 "#0087d7",
176 "#0087ff",
177 "#00af00",
178 "#00af5f",
179 "#00af87",
180 "#00afaf",
181 "#00afd7",
182 "#00afff",
183 "#00d700",
184 "#00d75f",
185 "#00d787",
186 "#00d7af",
187 "#00d7d7",
188 "#00d7ff",
189 "#00ff00",
190 "#00ff5f",
191 "#00ff87",
192 "#00ffaf",
193 "#00ffd7",
194 "#00ffff",
195 "#5f0000",
196 "#5f005f",
197 "#5f0087",
198 "#5f00af",
199 "#5f00d7",
200 "#5f00ff",
201 "#5f5f00",
202 "#5f5f5f",
203 "#5f5f87",
204 "#5f5faf",
205 "#5f5fd7",
206 "#5f5fff",
207 "#5f8700",
208 "#5f875f",
209 "#5f8787",
210 "#5f87af",
211 "#5f87d7",
212 "#5f87ff",
213 "#5faf00",
214 "#5faf5f",
215 "#5faf87",
216 "#5fafaf",
217 "#5fafd7",
218 "#5fafff",
219 "#5fd700",
220 "#5fd75f",
221 "#5fd787",
222 "#5fd7af",
223 "#5fd7d7",
224 "#5fd7ff",
225 "#5fff00",
226 "#5fff5f",
227 "#5fff87",
228 "#5fffaf",
229 "#5fffd7",
230 "#5fffff",
231 "#870000",
232 "#87005f",
233 "#870087",
234 "#8700af",
235 "#8700d7",
236 "#8700ff",
237 "#875f00",
238 "#875f5f",
239 "#875f87",
240 "#875faf",
241 "#875fd7",
242 "#875fff",
243 "#878700",
244 "#87875f",
245 "#878787",
246 "#8787af",
247 "#8787d7",
248 "#8787ff",
249 "#87af00",
250 "#87af5f",
251 "#87af87",
252 "#87afaf",
253 "#87afd7",
254 "#87afff",
255 "#87d700",
256 "#87d75f",
257 "#87d787",
258 "#87d7af",
259 "#87d7d7",
260 "#87d7ff",
261 "#87ff00",
262 "#87ff5f",
263 "#87ff87",
264 "#87ffaf",
265 "#87ffd7",
266 "#87ffff",
267 "#af0000",
268 "#af005f",
269 "#af0087",
270 "#af00af",
271 "#af00d7",
272 "#af00ff",
273 "#af5f00",
274 "#af5f5f",
275 "#af5f87",
276 "#af5faf",
277 "#af5fd7",
278 "#af5fff",
279 "#af8700",
280 "#af875f",
281 "#af8787",
282 "#af87af",
283 "#af87d7",
284 "#af87ff",
285 "#afaf00",
286 "#afaf5f",
287 "#afaf87",
288 "#afafaf",
289 "#afafd7",
290 "#afafff",
291 "#afd700",
292 "#afd75f",
293 "#afd787",
294 "#afd7af",
295 "#afd7d7",
296 "#afd7ff",
297 "#afff00",
298 "#afff5f",
299 "#afff87",
300 "#afffaf",
301 "#afffd7",
302 "#afffff",
303 "#d70000",
304 "#d7005f",
305 "#d70087",
306 "#d700af",
307 "#d700d7",
308 "#d700ff",
309 "#d75f00",
310 "#d75f5f",
311 "#d75f87",
312 "#d75faf",
313 "#d75fd7",
314 "#d75fff",
315 "#d78700",
316 "#d7875f",
317 "#d78787",
318 "#d787af",
319 "#d787d7",
320 "#d787ff",
321 "#d7af00",
322 "#d7af5f",
323 "#d7af87",
324 "#d7afaf",
325 "#d7afd7",
326 "#d7afff",
327 "#d7d700",
328 "#d7d75f",
329 "#d7d787",
330 "#d7d7af",
331 "#d7d7d7",
332 "#d7d7ff",
333 "#d7ff00",
334 "#d7ff5f",
335 "#d7ff87",
336 "#d7ffaf",
337 "#d7ffd7",
338 "#d7ffff",
339 "#ff0000",
340 "#ff005f",
341 "#ff0087",
342 "#ff00af",
343 "#ff00d7",
344 "#ff00ff",
345 "#ff5f00",
346 "#ff5f5f",
347 "#ff5f87",
348 "#ff5faf",
349 "#ff5fd7",
350 "#ff5fff",
351 "#ff8700",
352 "#ff875f",
353 "#ff8787",
354 "#ff87af",
355 "#ff87d7",
356 "#ff87ff",
357 "#ffaf00",
358 "#ffaf5f",
359 "#ffaf87",
360 "#ffafaf",
361 "#ffafd7",
362 "#ffafff",
363 "#ffd700",
364 "#ffd75f",
365 "#ffd787",
366 "#ffd7af",
367 "#ffd7d7",
368 "#ffd7ff",
369 "#ffff00",
370 "#ffff5f",
371 "#ffff87",
372 "#ffffaf",
373 "#ffffd7",
374 "#ffffff",
375 "#080808",
376 "#121212",
377 "#1c1c1c",
378 "#262626",
379 "#303030",
380 "#3a3a3a",
381 "#444444",
382 "#4e4e4e",
383 "#585858",
384 "#626262",
385 "#6c6c6c",
386 "#767676",
387 "#808080",
388 "#8a8a8a",
389 "#949494",
390 "#9e9e9e",
391 "#a8a8a8",
392 "#b2b2b2",
393 "#bcbcbc",
394 "#c6c6c6",
395 "#d0d0d0",
396 "#dadada",
397 "#e4e4e4",
398 "#eeeeee",
399}