v0.1.3 ANSI 16 indices and RGB to 16
This commit is contained in:
6 files changed
+75
-8
No files matched your search
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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")
|
||||||
}
|
}
|
||||||
|
|
||||||
// =====================================================================
|
// =====================================================================
|
||||||
|
|||||||
@@ -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,3 +1,3 @@
|
|||||||
module github.com/lixenwraith/color
|
module github.com/lixenwraith/color
|
||||||
|
|
||||||
go 1.26.0
|
go 1.27.1
|
||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in new issue
Block a user