From e5c78a87f2ddfe913ee9ee176c36c2ec095fc6b3 Mon Sep 17 00:00:00 2001 From: Lixen Wraith Date: Sun, 19 Jul 2026 05:48:02 -0400 Subject: [PATCH] v0.1.2 moved xterm color functionality to color package, adapters added for compatibility --- .../inline => cmd/example-inline}/main.go | 5 +- color.go | 109 ++--------------- go.mod | 2 +- go.sum | 2 + inline/README.md | 29 ++--- inline/inline.go | 28 +++-- inline/progress.go | 4 +- inline/style.go | 56 ++++++--- inline/sys_unix.go | 43 +++++++ inline/sys_unsupported.go | 13 ++ inline/sys_windows.go | 32 +++++ rgb_256.go | 115 ++++++------------ 12 files changed, 210 insertions(+), 228 deletions(-) rename {examples/inline => cmd/example-inline}/main.go (82%) create mode 100644 inline/sys_unix.go create mode 100644 inline/sys_unsupported.go create mode 100644 inline/sys_windows.go diff --git a/examples/inline/main.go b/cmd/example-inline/main.go similarity index 82% rename from examples/inline/main.go rename to cmd/example-inline/main.go index ac38bc1..4a1f968 100644 --- a/examples/inline/main.go +++ b/cmd/example-inline/main.go @@ -6,15 +6,14 @@ import ( "time" "github.com/lixenwraith/color" - "github.com/lixenwraith/terminal" "github.com/lixenwraith/terminal/inline" ) func main() { p := inline.New(os.Stdout) - name := inline.Fg(color.LightSkyBlue).Attr(terminal.AttrBold) - okSt := inline.Fg(color.LimeGreen).Attr(terminal.AttrBold) + name := inline.Fg(color.LightSkyBlue).Bold() + okSt := inline.Fg(color.LimeGreen).Bold() dim := inline.Fg(color.IronGray) pkgs := []string{"openssl", "zlib", "curl", "git", "go"} diff --git a/color.go b/color.go index da74a90..d33e612 100644 --- a/color.go +++ b/color.go @@ -1,11 +1,6 @@ package terminal -import ( - "sync" - "sync/atomic" - - "github.com/lixenwraith/color" -) +import "github.com/lixenwraith/color" // ColorMode indicates terminal color capability type ColorMode uint8 @@ -15,102 +10,14 @@ const ( ColorModeTrueColor // 24-bit RGB ) -// 6-bit quantized LUT for Redmean-based 256-color mapping -// 64×64×64 = 262,144 bytes, fits in L2 cache -const lut256Size = 64 * 64 * 64 - -// init() -> first-use build. Construction is ~63M Redmean evaluations -// and 256 KiB resident; truecolor sessions never read the table. -var ( - lut256Ptr atomic.Pointer[[lut256Size]uint8] - lut256Once sync.Once -) - -// lut256 returns the palette LUT, building it on first use. -// Fast path is an acquire load plus a predictable branch, and inlines into RGBTo256. -func lut256() *[lut256Size]uint8 { - if p := lut256Ptr.Load(); p != nil { - return p - } - return lut256Build() +// WarmPalette256 delegates to the color package's lazily evaluated 256-color LUT builder. +// Exists to preserve terminal API backwards-compatibility. +func WarmPalette256() { + color.WarmXterm256() } -// lut256Build populates the table. Cold path, kept out of line so lut256 stays inlinable. -// The release store publishes the fully written array to acquire loads in lut256. -// -//go:noinline -func lut256Build() *[lut256Size]uint8 { - lut256Once.Do(func() { - t := new([lut256Size]uint8) - for r := range 64 { - for g := range 64 { - for b := range 64 { - // Expand 6-bit to 8-bit (shift left 2, add 2 for midpoint) - c := color.RGB{ - R: uint8(r<<2 | 2), - G: uint8(g<<2 | 2), - B: uint8(b<<2 | 2), - } - t[r<<12|g<<6|b] = computeRedmean256(c) - } - } - } - lut256Ptr.Store(t) - }) - return lut256Ptr.Load() -} - -// WarmPalette256 forces LUT construction. Idempotent, safe for concurrent use. -// Call before the first render when ColorMode is ColorMode256. -// RGBTo256 runs on the render path under the terminal mutex; a lazy build there stalls the frame. -func WarmPalette256() { _ = lut256() } - -// computeRedmean256 finds the nearest 256-palette index using Redmean distance called from lut256Build, not init() -func computeRedmean256(c color.RGB) uint8 { - // Grayscale fast path - if c.R == c.G && c.G == c.B { - if c.R < 8 { - return 16 - } - if c.R > 238 { - return 231 - } - return uint8(232 + (int(c.R)-8)/10) - } - - bestIdx := uint8(16) - minDist := 1 << 30 - - // Search 6×6×6 cube (indices 16-231) - for i := range 216 { - cand := color.RGB{ - R: cubeValues[i/36], - G: cubeValues[(i/6)%6], - B: cubeValues[i%6], - } - if d := color.RedmeanDistance(c, cand); d < minDist { - minDist = d - bestIdx = uint8(16 + i) - } - } - - // Search grayscale ramp (indices 232-255) - for i := range 24 { - g := uint8(8 + i*10) - if d := color.RedmeanDistance(c, color.RGB{R: g, G: g, B: g}); d < minDist { - minDist = d - bestIdx = uint8(232 + i) - } - } - - return bestIdx -} - -// Color cube values for 6×6×6 palette (indices 16-231) -var cubeValues = [6]uint8{0, 95, 135, 175, 215, 255} - -// RGBTo256 converts RGB to nearest 256-color palette index. -// O(1) via the Redmean LUT; the first call builds it (see WarmPalette256). +// RGBTo256 delegates to the color package's perceptual quantizer. +// Exists to preserve terminal API backwards-compatibility. func RGBTo256(c color.RGB) uint8 { - return lut256()[int(c.R>>2)<<12|int(c.G>>2)<<6|int(c.B>>2)] + return color.RGBTo256(c) } diff --git a/go.mod b/go.mod index 9d7245a..e462937 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,7 @@ module github.com/lixenwraith/terminal go 1.26.0 require ( - github.com/lixenwraith/color v0.0.0-20260715115131-49124ee97342 + github.com/lixenwraith/color v0.0.0-20260719094342-615e11bc7897 golang.org/x/sys v0.47.0 golang.org/x/term v0.45.0 ) diff --git a/go.sum b/go.sum index 083d8bd..659d5b2 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/lixenwraith/color v0.0.0-20260715115131-49124ee97342 h1:KhjVSKhsHrngqeQ6sIcXX7HMmocixQ7ZZCumycbEKBo= github.com/lixenwraith/color v0.0.0-20260715115131-49124ee97342/go.mod h1:p02MsAGqlmZu3sc6BPYCKmaedF+lqBoijnuu5cOrYeo= +github.com/lixenwraith/color v0.0.0-20260719094342-615e11bc7897 h1:YkTK1vIzG6sqKRKaeUdkPyMT2laithY1d8tCqyId77U= +github.com/lixenwraith/color v0.0.0-20260719094342-615e11bc7897/go.mod h1:p02MsAGqlmZu3sc6BPYCKmaedF+lqBoijnuu5cOrYeo= golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.45.0 h1:NwWyBmoJCbfTHpxrWoZ9C6/VxOf7ic219I8xZZFdrf0= diff --git a/inline/README.md b/inline/README.md index af011c5..dbf7e5e 100644 --- a/inline/README.md +++ b/inline/README.md @@ -6,8 +6,8 @@ keeps owning the terminal. Intended for CLI tools (package managers, service tooling, build scripts) that want color and live status without a full-screen TUI. -Unix only (`//go:build unix`). Depends on the parent `terminal` package for -color types, capability detection, and RGB → 256 mapping. +Cross-platform (Unix, Windows). Depends only on the `color` package for +24-bit RGB inputs and 256-color automatic degradation. ## Model @@ -43,17 +43,17 @@ Value type, zero value is unstyled, builder-composable: | Function | Description | |---|---| -| `Fg(c terminal.RGB) Style` | Starts a style with foreground color. | -| `(s Style) Bg(c terminal.RGB) Style` | Adds background color. | -| `(s Style) Attr(a terminal.Attr) Style` | Adds attribute bits (`AttrBold`, `AttrDim`, ...). | +| `Fg(c color.RGB) Style` | Starts a style with foreground color. | +| `(s Style) Bg(c color.RGB) Style` | Adds background color. | +| `(s Style) Bold() Style` | Adds bold attribute (also: `Dim`, `Italic`, `Underline`, `Blink`, `Reverse`). | ```go -warn := inline.Fg(terminal.Amber).Attr(terminal.AttrBold) +warn := inline.Fg(color.Amber).Bold() p.Log("%s low disk space", p.Paint("warning:", warn)) ``` True color terminals get `38;2;R;G;B`; 256-color terminals get `38;5;N` via -Redmean mapping — same degradation path as the parent package. +Redmean mapping dynamically handled by the `color` package. ### Progress helpers @@ -90,16 +90,16 @@ import ( "os" "time" - "github.com/lixenwraith/terminal" + "github.com/lixenwraith/color" "github.com/lixenwraith/terminal/inline" ) func main() { p := inline.New(os.Stdout) - name := inline.Fg(terminal.LightSkyBlue).Attr(terminal.AttrBold) - okSt := inline.Fg(terminal.LimeGreen).Attr(terminal.AttrBold) - dim := inline.Fg(terminal.IronGray) + name := inline.Fg(color.LightSkyBlue).Bold() + okSt := inline.Fg(color.LimeGreen).Bold() + dim := inline.Fg(color.IronGray) pkgs := []string{"openssl", "zlib", "curl", "git", "go"} frame := 0 @@ -123,14 +123,10 @@ func main() { } ``` -Run in a terminal: the two-line status block animates in place while -completion lines accumulate above it. Piped (`go run . | cat`): only the -completion lines and the final summary appear, unstyled. - ## Notes - Width is measured in runes (`unicode/utf8`); East Asian wide characters and - combining marks are not width-aware — same limitation as `tui`. + combining marks are not width-aware. - Live block lines must occupy one visual row each: no `\n`, tabs, or control characters. Lines are truncated to terminal width automatically; embedded SGR from `Paint` is preserved through truncation. @@ -139,3 +135,4 @@ completion lines and the final summary appear, unstyled. the format string. - Ctrl-C mid-update leaves the live block on screen but the terminal in a normal state — no raw mode or screen buffer to restore. + diff --git a/inline/inline.go b/inline/inline.go index 0e2dbc7..929476b 100644 --- a/inline/inline.go +++ b/inline/inline.go @@ -1,5 +1,3 @@ -//go:build unix - // Package inline renders styled text and in-place progress in the normal // terminal scrollback: no raw mode, no alternate screen, no input handling, // no cursor hiding. Intended for CLI tools that want color and live status @@ -13,7 +11,7 @@ // unless overridden with SetColor(true), Log and Done print plainly. // // Width is measured in runes (unicode/utf8); wide and combining characters -// are not width-aware — same documented limitation as tui. +// are not width-aware. package inline import ( @@ -23,7 +21,15 @@ import ( "os" "sync" - "github.com/lixenwraith/terminal" + "github.com/lixenwraith/color" +) + +// Internal color capability representation +type colorMode uint8 + +const ( + colorMode256 colorMode = iota + colorModeTrueColor ) // Printer manages styled output and the live block. Safe for concurrent use. @@ -32,25 +38,25 @@ type Printer struct { w *bufio.Writer tty *os.File // non-nil when output is a terminal color bool - mode terminal.ColorMode + mode colorMode live []string // desired live block content drawn int // lines currently on screen (may be clamped below len(live)) } -// New creates a Printer for w. Terminal detection via WindowSize probe; +// New creates a Printer for w. Terminal detection via size probe; // styling defaults on for terminals with NO_COLOR unset. func New(w io.Writer) *Printer { p := &Printer{w: bufio.NewWriter(w)} if f, isFile := w.(*os.File); isFile { - if _, _, ok := terminal.WindowSize(f); ok { + if _, _, ok := windowSize(f); ok { p.tty = f } } p.color = p.tty != nil && os.Getenv("NO_COLOR") == "" - p.mode = terminal.DetectColorMode() + p.mode = detectColorMode() // Keep the LUT build out of the first Paint call - if p.color && p.mode == terminal.ColorMode256 { - terminal.WarmPalette256() + if p.color && p.mode == colorMode256 { + color.WarmXterm256() } return p } @@ -66,7 +72,7 @@ func (p *Printer) SetColor(on bool) { // Size returns terminal dimensions, 80×24 when unknown func (p *Printer) Size() (w, h int) { if p.tty != nil { - if w, h, ok := terminal.WindowSize(p.tty); ok { + if w, h, ok := windowSize(p.tty); ok { return w, h } } diff --git a/inline/progress.go b/inline/progress.go index 7884461..6567b42 100644 --- a/inline/progress.go +++ b/inline/progress.go @@ -1,5 +1,3 @@ -//go:build unix - package inline import "strings" @@ -36,7 +34,7 @@ func Bar(width int, pct float64, chars [3]rune) string { return b.String() } -// Braille frames; intentionally duplicated from tui (no tui dependency) +// Braille frames; standard monotonic iteration. var spinnerFrames = [...]string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"} // Spinner returns the frame for a monotonic counter diff --git a/inline/style.go b/inline/style.go index 728ccc0..098ae48 100644 --- a/inline/style.go +++ b/inline/style.go @@ -1,5 +1,3 @@ -//go:build unix - package inline import ( @@ -8,15 +6,27 @@ import ( "unicode/utf8" "github.com/lixenwraith/color" - "github.com/lixenwraith/terminal" +) + +// Attribute represents visual text modifiers for inline styling +type Attribute uint8 + +const ( + AttributeNone Attribute = 0 + Bold Attribute = 1 << 0 + Dim Attribute = 1 << 1 + Italic Attribute = 1 << 2 + Underline Attribute = 1 << 3 + Blink Attribute = 1 << 4 + Reverse Attribute = 1 << 5 ) // Style describes text appearance; zero value is unstyled. -// Composable: inline.Fg(terminal.Amber).Attr(terminal.AttrBold) +// Composable: inline.Fg(color.Amber).Bold().Underline() type Style struct { fg, bg color.RGB hasFg, hasBg bool - attr terminal.Attr + attr Attribute } // Fg starts a style with foreground color @@ -25,8 +35,23 @@ func Fg(c color.RGB) Style { return Style{fg: c, hasFg: true} } // Bg sets background color func (s Style) Bg(c color.RGB) Style { s.bg, s.hasBg = c, true; return s } -// Attr adds attribute bits -func (s Style) Attr(a terminal.Attr) Style { s.attr |= a; return s } +// Bold applies the bold attribute +func (s Style) Bold() Style { s.attr |= Bold; return s } + +// Dim applies the dim/faint attribute +func (s Style) Dim() Style { s.attr |= Dim; return s } + +// Italic applies the italic attribute +func (s Style) Italic() Style { s.attr |= Italic; return s } + +// Underline applies the underline attribute +func (s Style) Underline() Style { s.attr |= Underline; return s } + +// Blink applies the blink attribute +func (s Style) Blink() Style { s.attr |= Blink; return s } + +// Reverse applies the reverse video attribute +func (s Style) Reverse() Style { s.attr |= Reverse; return s } // Paint returns s styled for the detected terminal, unchanged when color // is disabled. Composes with Log: p.Log("%s %s", p.Paint("ok", st), name) @@ -44,36 +69,35 @@ func (p *Printer) Paint(s string, st Style) string { func (p *Printer) writeSGR(b *strings.Builder, s Style) { b.WriteString("\x1b[0") for _, m := range [...]struct { - bit terminal.Attr + bit Attribute code string }{ - {terminal.AttrBold, ";1"}, {terminal.AttrDim, ";2"}, - {terminal.AttrItalic, ";3"}, {terminal.AttrUnderline, ";4"}, - {terminal.AttrBlink, ";5"}, {terminal.AttrReverse, ";7"}, + {Bold, ";1"}, {Dim, ";2"}, + {Italic, ";3"}, {Underline, ";4"}, + {Blink, ";5"}, {Reverse, ";7"}, } { if s.attr&m.bit != 0 { b.WriteString(m.code) } } if s.hasFg { - if p.mode == terminal.ColorModeTrueColor { + if p.mode == colorModeTrueColor { fmt.Fprintf(b, ";38;2;%d;%d;%d", s.fg.R, s.fg.G, s.fg.B) } else { - fmt.Fprintf(b, ";38;5;%d", terminal.RGBTo256(s.fg)) + fmt.Fprintf(b, ";38;5;%d", color.RGBTo256(s.fg)) } } if s.hasBg { - if p.mode == terminal.ColorModeTrueColor { + if p.mode == colorModeTrueColor { fmt.Fprintf(b, ";48;2;%d;%d;%d", s.bg.R, s.bg.G, s.bg.B) } else { - fmt.Fprintf(b, ";48;5;%d", terminal.RGBTo256(s.bg)) + fmt.Fprintf(b, ";48;5;%d", color.RGBTo256(s.bg)) } } b.WriteByte('m') } // --- Width handling (internal, rune-count semantics) --- -// Handles only 'm'-terminated escapes — this package's own SGR output. // visibleLen counts runes excluding SGR sequences func visibleLen(s string) int { diff --git a/inline/sys_unix.go b/inline/sys_unix.go new file mode 100644 index 0000000..41110f4 --- /dev/null +++ b/inline/sys_unix.go @@ -0,0 +1,43 @@ +//go:build unix + +package inline + +import ( + "os" + "strings" + + "golang.org/x/sys/unix" +) + +func detectColorMode() colorMode { + colorterm := os.Getenv("COLORTERM") + if colorterm == "truecolor" || colorterm == "24bit" { + return colorModeTrueColor + } + + if os.Getenv("KITTY_WINDOW_ID") != "" || + os.Getenv("KONSOLE_VERSION") != "" || + os.Getenv("ITERM_SESSION_ID") != "" || + os.Getenv("ALACRITTY_WINDOW_ID") != "" || + os.Getenv("ALACRITTY_LOG") != "" || + os.Getenv("WEZTERM_PANE") != "" { + return colorModeTrueColor + } + + term := os.Getenv("TERM") + if strings.Contains(term, "truecolor") || + strings.Contains(term, "24bit") || + strings.Contains(term, "direct") { + return colorModeTrueColor + } + + return colorMode256 +} + +func windowSize(f *os.File) (w, h int, ok bool) { + ws, err := unix.IoctlGetWinsize(int(f.Fd()), unix.TIOCGWINSZ) + if err != nil || ws.Col == 0 { + return 0, 0, false + } + return int(ws.Col), int(ws.Row), true +} diff --git a/inline/sys_unsupported.go b/inline/sys_unsupported.go new file mode 100644 index 0000000..a7f094c --- /dev/null +++ b/inline/sys_unsupported.go @@ -0,0 +1,13 @@ +//go:build !unix && !windows + +package inline + +import "os" + +func detectColorMode() colorMode { + return colorMode256 +} + +func windowSize(f *os.File) (w, h int, ok bool) { + return 0, 0, false +} diff --git a/inline/sys_windows.go b/inline/sys_windows.go new file mode 100644 index 0000000..b5e56f1 --- /dev/null +++ b/inline/sys_windows.go @@ -0,0 +1,32 @@ +//go:build windows + +package inline + +import ( + "os" + + "golang.org/x/sys/windows" +) + +func detectColorMode() colorMode { + if os.Getenv("WT_SESSION") != "" || os.Getenv("WT_PROFILE_ID") != "" { + return colorModeTrueColor + } + if ct := os.Getenv("COLORTERM"); ct == "truecolor" || ct == "24bit" { + return colorModeTrueColor + } + return colorMode256 +} + +func windowSize(f *os.File) (w, h int, ok bool) { + var info windows.ConsoleScreenBufferInfo + if err := windows.GetConsoleScreenBufferInfo(windows.Handle(f.Fd()), &info); err != nil { + return 0, 0, false + } + width := int(info.Window.Right-info.Window.Left) + 1 + height := int(info.Window.Bottom-info.Window.Top) + 1 + if width < 1 || height < 1 { + return 0, 0, false + } + return width, height, true +} diff --git a/rgb_256.go b/rgb_256.go index 52efe94..a595767 100644 --- a/rgb_256.go +++ b/rgb_256.go @@ -1,93 +1,54 @@ package terminal -// Generic xterm 256-color palette indices without game semantics -// Game systems reference these via aliases in their own parameter files -// -// Color cube: index = 16 + 36*r + 6*g + b where r,g,b ∈ [0,5] -// Grayscale ramp: indices 232-255, level = 8 + 10*(index-232) -// -// Ordered dark-to-light within each hue group +import "github.com/lixenwraith/color" + +// Generic xterm 256-color constants proxied to the central color package +// to maintain backwards compatibility for the terminal package. const ( - // --- Blue --- - P256DeepNavy uint8 = 17 // (0,0,1) - P256DarkBlue uint8 = 18 // (0,0,2) - P256SteelBlue uint8 = 75 // (1,3,5) - P256LightBlue uint8 = 81 // (1,4,5) - - // --- Teal / Cyan --- - P256DeepTeal uint8 = 23 // (0,1,1) - P256Teal uint8 = 44 // (0,4,4) - P256Green uint8 = 46 // (0,5,0) - P256Cyan uint8 = 51 // (0,5,5) - P256LightCyan uint8 = 87 // (1,5,5) - - // --- Blue / Purple --- - P256CobaltBlue uint8 = 33 // (0,2,5) - P256DarkPurpleBlue uint8 = 54 // (1,0,2) - P256Indigo uint8 = 63 // (1,1,5) - P256Purple uint8 = 129 // (3,0,5) - P256Violet uint8 = 134 // (3,1,4) - P256MediumPurple uint8 = 135 // (3,1,5) - P256Orchid uint8 = 176 // (4,2,4) - - // --- Green / Yellow-Green --- - P256YellowGreen uint8 = 154 // (3,5,0) - - // --- Red --- - P256Maroon uint8 = 52 // (1,0,0) - P256DarkCrimson uint8 = 88 // (2,0,0) - P256Crimson uint8 = 160 // (4,0,0) - - // --- Red / Orange / Yellow --- - P256Red uint8 = 196 // (5,0,0) - P256Rose uint8 = 198 // (5,0,2) - P256RedOrange uint8 = 202 // (5,1,0) - P256Orange uint8 = 208 // (5,2,0) - P256Amber uint8 = 214 // (5,3,0) - P256Gold uint8 = 220 // (5,4,0) - P256Yellow uint8 = 226 // (5,5,0) - - // --- Orange / Brown --- - P256DarkAmber uint8 = 94 // (2,1,0) - - // --- Grayscale --- - P256Gray uint8 = 240 // Grayscale step 8, level ~88 + P256DeepNavy = color.P256DeepNavy + P256DarkBlue = color.P256DarkBlue + P256SteelBlue = color.P256SteelBlue + P256LightBlue = color.P256LightBlue + P256DeepTeal = color.P256DeepTeal + P256Teal = color.P256Teal + P256Green = color.P256Green + P256Cyan = color.P256Cyan + P256LightCyan = color.P256LightCyan + P256CobaltBlue = color.P256CobaltBlue + P256DarkPurpleBlue = color.P256DarkPurpleBlue + P256Indigo = color.P256Indigo + P256Purple = color.P256Purple + P256Violet = color.P256Violet + P256MediumPurple = color.P256MediumPurple + P256Orchid = color.P256Orchid + P256YellowGreen = color.P256YellowGreen + P256Maroon = color.P256Maroon + P256DarkCrimson = color.P256DarkCrimson + P256Crimson = color.P256Crimson + P256Red = color.P256Red + P256Rose = color.P256Rose + P256RedOrange = color.P256RedOrange + P256Orange = color.P256Orange + P256Amber = color.P256Amber + P256Gold = color.P256Gold + P256Yellow = color.P256Yellow + P256DarkAmber = color.P256DarkAmber + P256Gray = color.P256Gray ) // Cube256 returns the xterm 256-palette index for an RGB cube coordinate. -// r, g, b must be in [0,5]. Values outside that range are clamped. func Cube256(r, g, b uint8) uint8 { - if r > 5 { - r = 5 - } - if g > 5 { - g = 5 - } - if b > 5 { - b = 5 - } - return 16 + 36*r + 6*g + b + return color.Cube256(r, g, b) } // CubeRGB256 returns the (r, g, b) cube coordinates for a 256-palette color cube index. -// Index must be in [16,231]. Returns (0,0,0) for out-of-range indices. func CubeRGB256(index uint8) (r, g, b uint8) { - if index < 16 || index > 231 { - return 0, 0, 0 - } - n := index - 16 - r = n / 36 - g = (n % 36) / 6 - b = n % 6 - return r, g, b + return color.CubeRGB256(index) } // Gray256 returns the xterm 256-palette index for a grayscale step. -// step must be in [0,23] (maps to indices 232-255, levels 8-238). func Gray256(step uint8) uint8 { - if step > 23 { - step = 23 - } - return 232 + step -} \ No newline at end of file + return color.Gray256(step) +} +