v0.1.3 ANSI 16 indices and RGB to 16

This commit is contained in:
lixen committed 2026-10-04 02:50:07 -04:00
1 parent 615e11bc78
commit d6ba8b2105
6 files changed
+75 -8

No files matched your search

+11 -4
View File
@@ -77,15 +77,22 @@ var Accent = color.MustParseHex("#ff8800")
accent = { r = 255, g = 136, b = 0 } accent = { r = 255, g = 136, b = 0 }
``` ```
## With terminal ## Terminal palettes
`terminal.Cell` carries `color.RGB` directly. Quantization stays device-side: Quantization for terminals that cannot take 24-bit color:
```go ```go
idx := terminal.RGBTo256(color.EmeraldGreen) // xterm-256 index idx := color.RGBTo256(color.EmeraldGreen) // xterm-256 index, Redmean LUT
ansi := color.RGBTo16(color.EmeraldGreen) // ANSI 16 index, for text consoles
``` ```
Note: when `Cell.Attrs` sets `AttrFg256` / `AttrBg256`, `Cell.Fg.R` / `Cell.Bg.R` hold a palette index, not a channel. Such values are not colors and must not be passed to this package. `ANSIBlack` .. `ANSIBrightWhite` name the 16 indices (SGR `30-37`/`90-97`).
The terminal's theme picks their shade, so they suit semantic colors such as
log levels. `RGBTo16` measures against xterm's default shades.
`terminal.Cell` carries `color.RGB` directly. When `Cell.Attrs` sets
`AttrFg256` / `AttrBg256`, `Cell.Fg.R` / `Cell.Bg.R` hold a palette index, not
a channel. Such values are not colors and must not be passed to this package.
## Concurrency ## Concurrency
+1 -1
View File
@@ -54,7 +54,7 @@ func showGrayscaleRamp() {
idx := color.Gray256(step) idx := color.Gray256(step)
fmt.Print(block256(idx, 2)) fmt.Print(block256(idx, 2))
} }
fmt.Println("\n") fmt.Print("\n\n")
} }
// ===================================================================== // =====================================================================
+3 -2
View File
@@ -6,8 +6,9 @@
// image/color.Color, so values pass directly into image, draw, and GUI // image/color.Color, so values pass directly into image, draw, and GUI
// toolkit pipelines without conversion. // toolkit pipelines without conversion.
// //
// Device concerns — terminal capability detection, xterm-256 quantization, // Palette quantization (RGBTo256, RGBTo16) lives here; device concerns —
// SGR emission, framebuffer formats — belong in the consuming package. // terminal capability detection, SGR emission, framebuffer formats — belong
// in the consuming package.
// //
// # Operations // # Operations
// //
+1 -1
View File
@@ -1,3 +1,3 @@
module github.com/lixenwraith/color module github.com/lixenwraith/color
go 1.26.0 go 1.27.1
+41
View File
@@ -130,6 +130,47 @@ func computeRedmean256(c RGB) uint8 {
var cubeValues = [6]uint8{0, 95, 135, 175, 215, 255} var cubeValues = [6]uint8{0, 95, 135, 175, 215, 255}
// ANSI 16 color indices, sent as SGR 30-37/90-97 (40-47/100-107 background).
// The terminal's theme picks the shade; a text console renders them too.
const (
ANSIBlack uint8 = iota
ANSIRed
ANSIGreen
ANSIYellow
ANSIBlue
ANSIMagenta
ANSICyan
ANSIWhite
ANSIBrightBlack
ANSIBrightRed
ANSIBrightGreen
ANSIBrightYellow
ANSIBrightBlue
ANSIBrightMagenta
ANSIBrightCyan
ANSIBrightWhite
)
// xterm's default shades for the ANSI 16 indices, the reference for RGBTo16
var ansi16 = [16]RGB{
{0, 0, 0}, {205, 0, 0}, {0, 205, 0}, {205, 205, 0},
{0, 0, 238}, {205, 0, 205}, {0, 205, 205}, {229, 229, 229},
{127, 127, 127}, {255, 0, 0}, {0, 255, 0}, {255, 255, 0},
{92, 92, 255}, {255, 0, 255}, {0, 255, 255}, {255, 255, 255},
}
// RGBTo16 converts 24-bit RGB to the nearest ANSI 16 index (Redmean distance
// to xterm's default shades), for terminals limited to 16 colors.
func RGBTo16(c RGB) uint8 {
best, minDist := ANSIBlack, 1<<30
for i, cand := range ansi16 {
if d := RedmeanDistance(c, cand); d < minDist {
best, minDist = uint8(i), d
}
}
return best
}
// RGBTo256 converts 24-bit RGB to the nearest xterm-256 palette index. // RGBTo256 converts 24-bit RGB to the nearest xterm-256 palette index.
// O(1) via the Redmean LUT; the first call builds it. // O(1) via the Redmean LUT; the first call builds it.
func RGBTo256(c RGB) uint8 { func RGBTo256(c RGB) uint8 {
+18
View File
@@ -0,0 +1,18 @@
package color
import "testing"
// Each reference shade maps to its own index, so the constants, the table and
// the search agree; the named primaries land on the bright indices like xterm.
func TestRGBTo16MapsReferenceShadesToTheirIndex(t *testing.T) {
for i, c := range ansi16 {
if got := RGBTo16(c); got != uint8(i) {
t.Errorf("shade %d %s: index %d", i, c.Hex(), got)
}
}
for c, want := range map[RGB]uint8{Red: ANSIBrightRed, Black: ANSIBlack, White: ANSIBrightWhite, {170, 0, 0}: ANSIRed} {
if got := RGBTo16(c); got != want {
t.Errorf("%s: index %d, want %d", c.Hex(), got, want)
}
}
}