profile.go

  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}