1package ansi
2
3// ColorProfile describes the color sequences an output target can represent.
4// The zero value preserves the renderer's historical truecolor behavior.
5type ColorProfile uint8
6
7const (
8 // ColorProfileTrueColor emits direct RGB colors and preserves indexed colors.
9 ColorProfileTrueColor ColorProfile = iota
10 // ColorProfileANSI256 maps RGB colors to the fixed xterm 256-color palette.
11 ColorProfileANSI256
12 // ColorProfileANSI16 emits basic and bright 16-color SGR sequences.
13 ColorProfileANSI16
14 // ColorProfileMonochrome omits explicit colors while preserving attributes.
15 ColorProfileMonochrome
16)
17
18var ansi16Palette = [...]RGB{
19 {R: 0x00, G: 0x00, B: 0x00},
20 {R: 0x80, G: 0x00, B: 0x00},
21 {R: 0x00, G: 0x80, B: 0x00},
22 {R: 0x80, G: 0x80, B: 0x00},
23 {R: 0x00, G: 0x00, B: 0x80},
24 {R: 0x80, G: 0x00, B: 0x80},
25 {R: 0x00, G: 0x80, B: 0x80},
26 {R: 0xc0, G: 0xc0, B: 0xc0},
27 {R: 0x80, G: 0x80, B: 0x80},
28 {R: 0xff, G: 0x00, B: 0x00},
29 {R: 0x00, G: 0xff, B: 0x00},
30 {R: 0xff, G: 0xff, B: 0x00},
31 {R: 0x00, G: 0x00, B: 0xff},
32 {R: 0xff, G: 0x00, B: 0xff},
33 {R: 0x00, G: 0xff, B: 0xff},
34 {R: 0xff, G: 0xff, B: 0xff},
35}
36
37var ansi256CubeLevels = [...]uint8{0, 95, 135, 175, 215, 255}
38
39func rgbToANSI16(rgb RGB) int {
40 best, bestDistance := 0, colorDistance(rgb, ansi16Palette[0])
41 for index := 1; index < len(ansi16Palette); index++ {
42 if distance := colorDistance(rgb, ansi16Palette[index]); distance < bestDistance {
43 best, bestDistance = index, distance
44 }
45 }
46 return best
47}
48
49func rgbToANSI256(rgb RGB) int {
50 ri, r := nearestCubeLevel(rgb.R)
51 gi, g := nearestCubeLevel(rgb.G)
52 bi, b := nearestCubeLevel(rgb.B)
53 cubeIndex := 16 + 36*ri + 6*gi + bi
54 cubeDistance := colorDistance(rgb, RGB{R: r, G: g, B: b})
55
56 grayIndex, grayDistance := 232, colorDistance(rgb, RGB{R: 8, G: 8, B: 8})
57 for offset := 1; offset < 24; offset++ {
58 level := uint8(8 + 10*offset)
59 if distance := colorDistance(rgb, RGB{R: level, G: level, B: level}); distance < grayDistance {
60 grayIndex, grayDistance = 232+offset, distance
61 }
62 }
63 if grayDistance < cubeDistance {
64 return grayIndex
65 }
66 return cubeIndex
67}
68
69func nearestCubeLevel(value uint8) (int, uint8) {
70 best, bestDistance := 0, absInt(int(value)-int(ansi256CubeLevels[0]))
71 for index := 1; index < len(ansi256CubeLevels); index++ {
72 if distance := absInt(int(value) - int(ansi256CubeLevels[index])); distance < bestDistance {
73 best, bestDistance = index, distance
74 }
75 }
76 return best, ansi256CubeLevels[best]
77}
78
79func ansi256ToRGB(index int) (RGB, bool) {
80 if index < 0 || index > 255 {
81 return RGB{}, false
82 }
83 if index < len(ansi16Palette) {
84 return ansi16Palette[index], true
85 }
86 if index < 232 {
87 index -= 16
88 return RGB{
89 R: ansi256CubeLevels[index/36],
90 G: ansi256CubeLevels[(index/6)%6],
91 B: ansi256CubeLevels[index%6],
92 }, true
93 }
94 level := uint8(8 + (index-232)*10)
95 return RGB{R: level, G: level, B: level}, true
96}
97
98func colorDistance(a, b RGB) int {
99 dr := int(a.R) - int(b.R)
100 dg := int(a.G) - int(b.G)
101 db := int(a.B) - int(b.B)
102 return dr*dr + dg*dg + db*db
103}
104
105func absInt(value int) int {
106 if value < 0 {
107 return -value
108 }
109 return value
110}