aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md27
-rw-r--r--chart.go507
-rw-r--r--color/color.go103
-rw-r--r--go.mod7
-rw-r--r--go.sum4
5 files changed, 619 insertions, 29 deletions
diff --git a/README.md b/README.md
index a9e56ce..fad4997 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,25 @@
-# xterm-color-chart
-XTerm 256 color chart
+# xterm265-color-palette
-![alt text](https://raw.githubusercontent.com/kutuluk/xterm-color-chart/master/screenshot.png "screenshot") \ No newline at end of file
+A TUI XTerm 256 color palette tool. This is just a small fork of [xterm-color-chart](https://github.com/kutuluk/xterm-color-chart) by Evgeniy Pavlov.
+
+Click on colors to select them and build a palette.
+`h` to cycle harmony modes
+`<Space>` to make a palette based on the first color
+`e` to try to extend the palette with similar colors
+`t` to toggle the 16 system colors as palette seeds/outputs (off by default; they depend on your terminal theme)
+`q` to quit
+
+## Build
+
+Requires [Go](https://go.dev/).
+
+```sh
+git clone https://codeberg.org/historia/xterm256-color-palette
+cd xterm256-color-palette
+go build -o xtp .
+./xtp
+```
+
+## License
+
+MIT
diff --git a/chart.go b/chart.go
index dd4ac10..ba85a15 100644
--- a/chart.go
+++ b/chart.go
@@ -3,8 +3,10 @@ package main
import (
"fmt"
"math"
+ "math/rand"
+ "time"
- "github.com/kutuluk/xterm-color-chart/color"
+ "codeberg.org/historia/xterm256-color-palette/color"
"github.com/nsf/termbox-go"
)
@@ -44,6 +46,196 @@ func termNative(c int) termbox.Attribute {
return termbox.Attribute(c + 1)
}
+type hitRegion struct {
+ x, y, w, h int
+ leftAction func(x, y int)
+ rightAction func(x, y int)
+}
+
+var hitRegions []hitRegion
+
+func addHitRegion(x, y, w, h int, leftAction, rightAction func(x, y int)) {
+ hitRegions = append(hitRegions, hitRegion{x, y, w, h, leftAction, rightAction})
+}
+
+func hitTest(px, py int) *hitRegion {
+ for i := len(hitRegions) - 1; i >= 0; i-- {
+ r := &hitRegions[i]
+ if px >= r.x && px < r.x+r.w && py >= r.y && py < r.y+r.h {
+ return r
+ }
+ }
+ return nil
+}
+
+var selectedColors []int
+var paletteScroll int
+var autoScrollPalette bool
+var paletteLeft int
+var paletteTop int
+var lastMouseEvent time.Time
+var dragIndex = -1
+var dragY int
+
+func addSelectedColor(c int) {
+ for _, sc := range selectedColors {
+ if sc == c {
+ return
+ }
+ }
+ selectedColors = append(selectedColors, c)
+ autoScrollPalette = true
+}
+
+func removeSelectedColor(i int) {
+ if i >= 0 && i < len(selectedColors) {
+ selectedColors = append(selectedColors[:i], selectedColors[i+1:]...)
+ }
+ if paletteScroll > len(selectedColors)-1 {
+ paletteScroll = len(selectedColors) - 1
+ }
+ if paletteScroll < 0 {
+ paletteScroll = 0
+ }
+}
+
+func moveSelectedColor(fromIdx, toIdx int) {
+ if fromIdx < 0 || fromIdx >= len(selectedColors) || toIdx < 0 || toIdx >= len(selectedColors) || fromIdx == toIdx {
+ return
+ }
+ c := selectedColors[fromIdx]
+ selectedColors = append(selectedColors[:fromIdx], selectedColors[fromIdx+1:]...)
+ selectedColors = append(selectedColors[:toIdx], append([]int{c}, selectedColors[toIdx:]...)...)
+}
+
+var harmonies = []struct {
+ name string
+ offsets []float64
+}{
+ {"complementary", []float64{0, 180}},
+ {"analogous", []float64{0, 330, 30}},
+ {"triadic", []float64{0, 120, 240}},
+ {"split-comp", []float64{0, 150, 210}},
+ {"tetradic", []float64{0, 60, 180, 240}},
+ {"square", []float64{0, 90, 180, 270}},
+ {"monochromatic", nil},
+}
+
+var currentHarmony = 2 // triadic
+
+// включать ли 16 системных цветов терминала в генерацию палитр
+// (их реальный вид зависит от темы терминала)
+var useSystemColors bool
+
+// привязка LCh-цвета к палитре xterm с уменьшением насыщенности
+// до попадания в sRGB-охват (сохраняя светлоту и тон)
+func snapColor(lch color.LChColor) int {
+ for lch.C > 0.5 && !color.InGamut(lch) {
+ lch.C *= 0.95
+ }
+ var col int
+ if useSystemColors {
+ _, col = color.ApproximateAll(lch, comparers[currentComparer])
+ } else {
+ _, col = color.Approximate(lch, comparers[currentComparer])
+ }
+ return col
+}
+
+// генерация гармоничной палитры в пространстве LCh
+func generateHarmony() {
+ har := harmonies[currentHarmony]
+
+ var base color.LChColor
+ seeded := false
+ if len(selectedColors) > 0 {
+ c := selectedColors[0]
+ if useSystemColors || c-1 >= 16 {
+ base = color.TermLab(c).LCh()
+ seeded = true
+ }
+ }
+ if !seeded {
+ rnd := color.LChColor{
+ L: 40.0 + 40.0*rand.Float64(),
+ C: 40.0 + 50.0*rand.Float64(),
+ H: 360.0 * rand.Float64(),
+ }
+ // привязка к ближайшему цвету xterm, чтобы повторная генерация давала тот же результат
+ c := snapColor(rnd)
+ base = color.TermLab(c).LCh()
+ }
+
+ selectedColors = selectedColors[:0]
+ paletteScroll = 0
+
+ if har.offsets == nil {
+ // монохроматическая: один тон, разная светлота
+ for i := 0; i < 5; i++ {
+ l := 20.0 + 15.0*float64(i)
+ addSelectedColor(snapColor(color.LChColor{L: l, C: base.C, H: base.H}))
+ }
+ } else {
+ for _, off := range har.offsets {
+ hue := math.Mod(base.H+off, 360.0)
+ addSelectedColor(snapColor(color.LChColor{L: base.L, C: base.C, H: hue}))
+ }
+ }
+}
+
+// расширение палитры: для каждого цвета добавляются тень, блик и приглушенный
+// вариант того же тона, сгруппированные сразу после исходного цвета
+func expandPalette() {
+ if len(selectedColors) == 0 {
+ return
+ }
+
+ clampL := func(l float64) float64 {
+ if l < 10.0 {
+ return 10.0
+ }
+ if l > 90.0 {
+ return 90.0
+ }
+ return l
+ }
+
+ expanded := make([]int, 0, len(selectedColors)*4)
+ contains := func(c int) bool {
+ for _, sc := range expanded {
+ if sc == c {
+ return true
+ }
+ }
+ return false
+ }
+
+ for _, c := range selectedColors {
+ if !contains(c) {
+ expanded = append(expanded, c)
+ }
+ if !useSystemColors && c-1 < 16 {
+ continue
+ }
+ lch := color.TermLab(c).LCh()
+ variants := []color.LChColor{
+ {L: clampL(lch.L - 25.0), C: lch.C, H: lch.H},
+ {L: clampL(lch.L + 25.0), C: lch.C, H: lch.H},
+ {L: lch.L, C: lch.C * 0.5, H: lch.H},
+ }
+ for _, v := range variants {
+ col := snapColor(v)
+ if !contains(col) {
+ expanded = append(expanded, col)
+ }
+ }
+ }
+
+ selectedColors = expanded
+ paletteScroll = 0
+ autoScrollPalette = false
+}
+
func printString(x, y int, s string) {
// TODO срабатывает перенос на следующую строку при достижении правого края
// и ошибка при выходе из границ буфера снизу
@@ -112,6 +304,13 @@ func DrawColor(x, y int, c int) {
printLineAttr(x+1, y, fmt.Sprintf("%03d #%s", int(c), color.XtermRGBPalette[c]), fg, termbox.Attribute(c))
}
+var (
+ boxCacheSize int
+ boxCacheLight int
+ boxCacheComp int
+ boxCacheBlocks []struct{ fg, bg termbox.Attribute }
+)
+
func DrawBox(left, top, d, l int) {
var approxer string
switch currentComparer {
@@ -123,30 +322,44 @@ func DrawBox(left, top, d, l int) {
approxer = "CIE2000"
}
- r := d / 2
- for y := 0; y < d; y++ {
- for x := 0; x < d; x++ {
- a := 100.0 * float64(x-r) / float64(r)
- b := -100.0 * float64(y-r) / float64(r)
- _, c := color.Approximate(color.LabColor{float64(l), a, b}, comparers[currentComparer])
- //termbox.SetCell(x*2+left, y+top, ' ', background, termbox.Attribute(color))
- //termbox.SetCell(x*2+left+1, y+top, ' ', background, termbox.Attribute(color))
- fg := int(background)
- if y == 1 || y == 2 || y == d-2 {
- fgLab := color.XtermRGBPalette[c-17].Lab()
- if l > 50 {
- fgLab.L -= 50
- //fgLab.L = 20
- } else {
- fgLab.L += 50
- //fgLab.L = 70
+ hit := d == boxCacheSize && l == boxCacheLight && currentComparer == boxCacheComp && boxCacheBlocks != nil
+
+ if hit {
+ idx := 0
+ for y := 0; y < d; y++ {
+ for x := 0; x < d; x++ {
+ blk := boxCacheBlocks[idx]
+ termbox.SetCell(x*2+left, y+top, ' ', blk.fg, blk.bg)
+ termbox.SetCell(x*2+left+1, y+top, ' ', blk.fg, blk.bg)
+ idx++
+ }
+ }
+ } else {
+ r := d / 2
+ boxCacheBlocks = make([]struct{ fg, bg termbox.Attribute }, d*d)
+ idx := 0
+ for y := 0; y < d; y++ {
+ for x := 0; x < d; x++ {
+ a := 100.0 * float64(x-r) / float64(r)
+ b := -100.0 * float64(y-r) / float64(r)
+ _, c := color.Approximate(color.LabColor{float64(l), a, b}, comparers[currentComparer])
+ fg := int(background)
+ if y == 1 || y == 2 || y == d-2 {
+ fgLab := color.TermLab(c)
+ if l > 50 {
+ fgLab.L -= 50
+ } else {
+ fgLab.L += 50
+ }
+ _, fg = color.Approximate(fgLab, color.DeltaCIE2000{})
}
- _, fg = color.Approximate(fgLab, color.DeltaCIE2000{})
+ boxCacheBlocks[idx] = struct{ fg, bg termbox.Attribute }{termbox.Attribute(fg), termbox.Attribute(c)}
+ termbox.SetCell(x*2+left, y+top, ' ', termbox.Attribute(fg), termbox.Attribute(c))
+ termbox.SetCell(x*2+left+1, y+top, ' ', termbox.Attribute(fg), termbox.Attribute(c))
+ idx++
}
- termbox.SetCell(x*2+left, y+top, ' ', termbox.Attribute(fg), termbox.Attribute(c))
- termbox.SetCell(x*2+left+1, y+top, ' ', termbox.Attribute(fg), termbox.Attribute(c))
}
-
+ boxCacheSize, boxCacheLight, boxCacheComp = d, l, currentComparer
}
labLabel := "CIE L*a*b color space"
@@ -212,6 +425,10 @@ func DrawCircle(left, top, r int, l float64) {
func DrawColorCell(x, y, c int) {
termbox.SetCell(x, y, ' ', termbox.Attribute(1), termbox.Attribute(c))
termbox.SetCell(x+1, y, ' ', termbox.Attribute(1), termbox.Attribute(c))
+ val := c
+ addHitRegion(x, y, 2, 1, func(_, _ int) {
+ addSelectedColor(val)
+ }, nil)
}
// left, top - левый верхний угол отрисовки палитры
@@ -286,6 +503,108 @@ func Fill(x1, y1, x2, y2 int, fg, bg termbox.Attribute) {
}
}
+func drawPaletteEntry(x, y, c, idx int) {
+ termbox.SetCell(x, y, ' ', termbox.Attribute(1), termbox.Attribute(c))
+ termbox.SetCell(x+1, y, ' ', termbox.Attribute(1), termbox.Attribute(c))
+ label := fmt.Sprintf("%03d / %02X", c-1, c-1)
+ if dragIndex == idx {
+ printLineAttr(x+3, y, label, background, background+7)
+ } else {
+ printLineAttr(x+3, y, label, text, background)
+ }
+ addHitRegion(x, y, 3+len(label), 1,
+ func(_, _ int) {
+ if dragIndex < 0 {
+ dragIndex = idx
+ }
+ },
+ func(_, _ int) {
+ removeSelectedColor(idx)
+ if dragIndex == idx {
+ dragIndex = -1
+ } else if dragIndex > idx {
+ dragIndex--
+ }
+ })
+}
+
+// панель пользовательской палитры
+func DrawUserPalette(left, top int) {
+ paletteLeft = left
+ paletteTop = top
+
+ printLineAttr(left, top, fmt.Sprintf("Palette (%d) [%s]", len(selectedColors), harmonies[currentHarmony].name), text, background)
+
+ n := len(selectedColors)
+ if n == 0 {
+ paletteScroll = 0
+ autoScrollPalette = false
+ printLineAttr(left, top+2, "Click a color to add it here,", background+6, background)
+ printLineAttr(left, top+3, "or press Space to generate", background+6, background)
+ return
+ }
+
+ rows := screen.Height - (top + 2)
+ if rows <= 0 {
+ autoScrollPalette = false
+ return
+ }
+
+ if n <= rows {
+ paletteScroll = 0
+ autoScrollPalette = false
+ for i, c := range selectedColors {
+ drawPaletteEntry(left, top+2+i, c, i)
+ }
+ return
+ }
+
+ maxScroll := n - (rows - 1)
+ if maxScroll > n-1 {
+ maxScroll = n - 1
+ }
+ if autoScrollPalette {
+ paletteScroll = maxScroll
+ autoScrollPalette = false
+ }
+ if paletteScroll > maxScroll {
+ paletteScroll = maxScroll
+ }
+ if paletteScroll < 0 {
+ paletteScroll = 0
+ }
+
+ visible := rows
+ if paletteScroll > 0 {
+ visible--
+ }
+ if paletteScroll+visible < n {
+ visible--
+ }
+ if visible < 1 {
+ visible = 1
+ }
+
+ y := top + 2
+ if paletteScroll > 0 {
+ printLineAttr(left, y, fmt.Sprintf("^ %d more", paletteScroll), background+7, background)
+ y++
+ }
+
+ end := paletteScroll + visible
+ if end > n {
+ end = n
+ }
+ for i := paletteScroll; i < end; i++ {
+ drawPaletteEntry(left, y, selectedColors[i], i)
+ y++
+ }
+
+ if below := n - end; below > 0 && y < screen.Height {
+ printLineAttr(left, y, fmt.Sprintf("v %d more", below), background+7, background)
+ }
+}
+
var (
text, background termbox.Attribute
lightness int
@@ -293,6 +612,7 @@ var (
)
func DrawChart() {
+ hitRegions = hitRegions[:0]
screen.Clear(text, background)
//Fill(0, 0, screen.Width-1, screen.Height-1, text, background)
@@ -313,6 +633,27 @@ func DrawChart() {
DrawBox(2, 2, boxSize, lightness)
// http://snag.gy/XMro8.jpg - картинка для сравнения
+ boxLeft, boxTop := 2, 2
+ boxW, boxH := boxSize*2, boxSize
+ bs := boxSize
+ lt := lightness
+ addHitRegion(boxLeft, boxTop, boxW, boxH, func(px, py int) {
+ cx := (px - boxLeft) / 2
+ cy := py - boxTop
+ r := bs / 2
+ if r > 0 && cx >= 0 && cx < bs && cy >= 0 && cy < bs {
+ a := 100.0 * float64(cx-r) / float64(r)
+ b := -100.0 * float64(cy-r) / float64(r)
+ _, col := color.Approximate(color.LabColor{float64(lt), a, b}, comparers[currentComparer])
+ addSelectedColor(col)
+ }
+ }, nil)
+
+ sysState := "off"
+ if useSystemColors {
+ sysState = "on"
+ }
+
helpText := []string{
"Control keys:",
"",
@@ -324,6 +665,22 @@ func DrawChart() {
"CIE94 - middle",
"CIE2000 - slowest",
"",
+ "Click a color to add it to the palette",
+ "L-click palette entry to pick up / drop",
+ "R-click palette entry to remove",
+ "Backspace - remove last palette entry",
+ "Space - generate harmony palette",
+ " (seeds from first entry if any)",
+ "h - change harmony mode",
+ "e - expand palette with similar colors",
+ "t - use 16 system colors in generated",
+ fmt.Sprintf(" palettes, as seeds and outputs (%s).", sysState),
+ " When off, a system color seed is",
+ " replaced by a random one. Note: system",
+ " colors depend on your terminal theme,",
+ " so results assume xterm defaults",
+ "q/Esc/F10 - quit",
+ "Wheel/PgUp/PgDn - scroll palette",
"",
}
@@ -341,6 +698,8 @@ func DrawChart() {
DrawPalette(xc, yc, 3, true)
}
+ DrawUserPalette(xc, yc+3*7+11)
+
screen.Flush()
}
@@ -355,7 +714,7 @@ func main() {
}
defer termbox.Close()
termbox.SetOutputMode(termbox.Output256)
- //termbox.SetInputMode(termbox.InputEsc)
+ termbox.SetInputMode(termbox.InputEsc | termbox.InputMouse)
background = termNative(234)
text = background + 12
@@ -374,7 +733,16 @@ func main() {
switch ev.Key {
case termbox.KeyCtrlQ, termbox.KeyF10:
loop = false
- case termbox.KeyCtrlH, termbox.KeyF1:
+ case termbox.KeyEsc:
+ if time.Since(lastMouseEvent) > 150*time.Millisecond {
+ loop = false
+ }
+ case termbox.KeyBackspace, termbox.KeyBackspace2:
+ if len(selectedColors) > 0 {
+ removeSelectedColor(len(selectedColors) - 1)
+ DrawChart()
+ }
+ case termbox.KeyF1:
helpVisible = !helpVisible
DrawChart()
case termbox.KeyArrowUp:
@@ -401,6 +769,97 @@ func main() {
currentComparer = 0
}
DrawChart()
+ case termbox.KeySpace:
+ generateHarmony()
+ DrawChart()
+ case termbox.KeyPgup:
+ if paletteScroll > 0 {
+ paletteScroll--
+ DrawChart()
+ }
+ case termbox.KeyPgdn:
+ paletteScroll++
+ DrawChart()
+ default:
+ switch ev.Ch {
+ case 'q', 'Q':
+ if time.Since(lastMouseEvent) > 150*time.Millisecond {
+ loop = false
+ }
+ case 'h', 'H':
+ currentHarmony = (currentHarmony + 1) % len(harmonies)
+ DrawChart()
+ case 'e', 'E':
+ expandPalette()
+ DrawChart()
+ case 't', 'T':
+ useSystemColors = !useSystemColors
+ DrawChart()
+ }
+ }
+ case termbox.EventMouse:
+ lastMouseEvent = time.Now()
+ switch ev.Key {
+ case termbox.MouseLeft:
+ if dragIndex < 0 {
+ if r := hitTest(ev.MouseX, ev.MouseY); r != nil && r.leftAction != nil {
+ r.leftAction(ev.MouseX, ev.MouseY)
+ }
+ }
+ dragY = ev.MouseY
+ if dragIndex >= 0 {
+ target := func() int {
+ n := len(selectedColors)
+ rows := screen.Height - (paletteTop + 2)
+ if n <= rows {
+ t := dragY - (paletteTop + 2)
+ if t < 0 {
+ t = 0
+ }
+ if t >= n {
+ t = n - 1
+ }
+ return t
+ }
+ firstEntryY := paletteTop + 2
+ if paletteScroll > 0 {
+ firstEntryY++
+ }
+ t := paletteScroll + (dragY - firstEntryY)
+ if t < 0 {
+ t = 0
+ }
+ if t >= n {
+ t = n - 1
+ }
+ return t
+ }()
+ if target != dragIndex {
+ moveSelectedColor(dragIndex, target)
+ dragIndex = target
+ }
+ }
+ DrawChart()
+ case termbox.MouseRight:
+ if r := hitTest(ev.MouseX, ev.MouseY); r != nil && r.rightAction != nil {
+ r.rightAction(ev.MouseX, ev.MouseY)
+ DrawChart()
+ }
+ case termbox.MouseRelease:
+ if dragIndex >= 0 {
+ dragIndex = -1
+ DrawChart()
+ }
+ case termbox.MouseWheelUp:
+ if ev.MouseX >= paletteLeft && paletteScroll > 0 {
+ paletteScroll--
+ DrawChart()
+ }
+ case termbox.MouseWheelDown:
+ if ev.MouseX >= paletteLeft && len(selectedColors) > 0 {
+ paletteScroll++
+ DrawChart()
+ }
}
case termbox.EventResize:
DrawChart()
diff --git a/color/color.go b/color/color.go
index a3cbd16..243869b 100644
--- a/color/color.go
+++ b/color/color.go
@@ -99,9 +99,52 @@ func (xyz XYZColor) String() string {
return fmt.Sprintf("XYZ:{%4.7f %4.7f %4.7f}", xyz.X, xyz.Y, xyz.Z)
}
+// линейные RGB-компоненты (без гамма-коррекции и ограничения диапазона)
+func (xyz XYZColor) rgbLinear() (float64, float64, float64) {
+ //
+ // [R] [ 3.2406 -1.5372 -0.4986] [X]
+ // [G] = [-0.9689 1.8758 0.0415] x [Y]
+ // [B] [ 0.0557 -0.2040 1.0570] [Z]
+ //
+ // Observer. = 2°, Illuminant = D65
+ r := xyz.X*3.2406 + xyz.Y*-1.5372 + xyz.Z*-0.4986
+ g := xyz.X*-0.9689 + xyz.Y*1.8758 + xyz.Z*0.0415
+ b := xyz.X*0.0557 + xyz.Y*-0.2040 + xyz.Z*1.0570
+ return r, g, b
+}
+
func (xyz XYZColor) RGB() RGBColor {
- // TODO!!!
- return RGBColor{0, 0, 0}
+
+ f := func(n float64) int {
+ if n > 0.0031308 {
+ n = 1.055*math.Pow(n, 1.0/2.4) - 0.055
+ } else {
+ n = n * 12.92
+ }
+ v := int(math.Round(n * 255.0))
+ if v < 0 {
+ v = 0
+ }
+ if v > 255 {
+ v = 255
+ }
+ return v
+ }
+
+ r, g, b := xyz.rgbLinear()
+ return RGBColor{f(r), f(g), f(b)}
+}
+
+// InGamut сообщает, представим ли цвет в sRGB
+func InGamut(c Colorer) bool {
+ const tolerance = 1e-4
+ r, g, b := c.XYZ().rgbLinear()
+ for _, n := range [3]float64{r, g, b} {
+ if n < -tolerance || n > 1.0+tolerance {
+ return false
+ }
+ }
+ return true
}
func (xyz XYZColor) XYZ() XYZColor {
@@ -367,6 +410,35 @@ func Approximate(color Colorer, comparer Comparer) (float64, int) {
return bestdist, best + 17
}
+// ApproximateAll ищет ближайший цвет среди всех 256 цветов xterm,
+// включая 16 системных (их реальный вид зависит от темы терминала)
+func ApproximateAll(color Colorer, comparer Comparer) (float64, int) {
+ bestdist, best := Approximate(color, comparer)
+ for i, applicant := range SystemLabPalette {
+ if dist := comparer.Compare(color, applicant); dist < bestdist {
+ best, bestdist = i+1, dist
+ }
+ }
+ return bestdist, best
+}
+
+// TermRGB возвращает RGB-значение по termbox-атрибуту (1..256).
+// Для системных цветов используются стандартные значения xterm.
+func TermRGB(c int) RGBColor {
+ if c-1 < 16 {
+ return SystemRGBPalette[c-1]
+ }
+ return XtermRGBPalette[c-17]
+}
+
+// TermLab возвращает Lab-значение по termbox-атрибуту (1..256)
+func TermLab(c int) LabColor {
+ if c-1 < 16 {
+ return SystemLabPalette[c-1]
+ }
+ return XtermLabPalette[c-17]
+}
+
//color = round(36 * (r * 5) + 6 * (g * 5) + (b * 5) + 16)
func HackApproximate(color Colorer) int {
c := color.RGB()
@@ -623,6 +695,28 @@ var (
*/
XtermRGBPalette [240]RGBColor
XtermLabPalette [240]LabColor
+
+ // standard xterm defaults for the 16 system colors (0-15);
+ // actual appearance depends on the terminal theme
+ SystemRGBPalette = [16]RGBColor{
+ {0x00, 0x00, 0x00}, // 0 black
+ {0xCD, 0x00, 0x00}, // 1 red
+ {0x00, 0xCD, 0x00}, // 2 green
+ {0xCD, 0xCD, 0x00}, // 3 yellow
+ {0x00, 0x00, 0xEE}, // 4 blue
+ {0xCD, 0x00, 0xCD}, // 5 magenta
+ {0x00, 0xCD, 0xCD}, // 6 cyan
+ {0xE5, 0xE5, 0xE5}, // 7 white
+ {0x7F, 0x7F, 0x7F}, // 8 bright black
+ {0xFF, 0x00, 0x00}, // 9 bright red
+ {0x00, 0xFF, 0x00}, // 10 bright green
+ {0xFF, 0xFF, 0x00}, // 11 bright yellow
+ {0x5C, 0x5C, 0xFF}, // 12 bright blue
+ {0xFF, 0x00, 0xFF}, // 13 bright magenta
+ {0x00, 0xFF, 0xFF}, // 14 bright cyan
+ {0xFF, 0xFF, 0xFF}, // 15 bright white
+ }
+ SystemLabPalette [16]LabColor
)
func init() {
@@ -646,4 +740,9 @@ func init() {
for i, color := range XtermRGBPalette {
XtermLabPalette[i] = color.Lab()
}
+
+ // calculate Lab palette for the 16 system colors
+ for i, color := range SystemRGBPalette {
+ SystemLabPalette[i] = color.Lab()
+ }
}
diff --git a/go.mod b/go.mod
new file mode 100644
index 0000000..7b200c8
--- /dev/null
+++ b/go.mod
@@ -0,0 +1,7 @@
+module codeberg.org/historia/xterm256-color-palette
+
+go 1.26.3
+
+require github.com/nsf/termbox-go v1.1.1
+
+require github.com/mattn/go-runewidth v0.0.9 // indirect
diff --git a/go.sum b/go.sum
new file mode 100644
index 0000000..509f0d3
--- /dev/null
+++ b/go.sum
@@ -0,0 +1,4 @@
+github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0=
+github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/nsf/termbox-go v1.1.1 h1:nksUPLCb73Q++DwbYUBEglYBRPZyoXJdrj5L+TkjyZY=
+github.com/nsf/termbox-go v1.1.1/go.mod h1:T0cTdVuOwf7pHQNtfhnEbzHbcNyCEcVU4YPpouCbVxo=