v0.1.2 xterm 256 color addition, examples in cmd dir
This commit is contained in:
@@ -0,0 +1,145 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/lixenwraith/color"
|
||||
)
|
||||
|
||||
const reset = "\x1b[0m"
|
||||
|
||||
// bg256 returns the SGR sequence for an 8-bit xterm-256 background.
|
||||
// This is the sequence used by naked terminals (TTY, basic SSH).
|
||||
func bg256(idx uint8) string { return fmt.Sprintf("\x1b[48;5;%dm", idx) }
|
||||
|
||||
// bgRGB returns the SGR sequence for a 24-bit truecolor background.
|
||||
func bgRGB(c color.RGB) string { return fmt.Sprintf("\x1b[48;2;%d;%d;%dm", c.R, c.G, c.B) }
|
||||
|
||||
// block256 renders n background-colored spaces using the 256-color palette.
|
||||
func block256(idx uint8, n int) string { return bg256(idx) + strings.Repeat(" ", n) + reset }
|
||||
|
||||
// blockRGB renders n background-colored spaces using truecolor.
|
||||
func blockRGB(c color.RGB, n int) string { return bgRGB(c) + strings.Repeat(" ", n) + reset }
|
||||
|
||||
// =====================================================================
|
||||
// PART 1: 256 Color Mechanics
|
||||
// =====================================================================
|
||||
|
||||
func show256Cube() {
|
||||
fmt.Println("── Part 1: xterm-256 Color Cube (6x6x6) ──")
|
||||
fmt.Println("Displaying the 216-color cube layout (Indices 16-231).")
|
||||
|
||||
// The standard xterm-256 cube consists of 6 "slices" of red,
|
||||
// containing 6x6 grids of green and blue.
|
||||
for r := uint8(0); r < 6; r++ {
|
||||
for g := uint8(0); g < 6; g++ {
|
||||
for b := uint8(0); b < 6; b++ {
|
||||
// Get exact index using the pure math function
|
||||
idx := color.Cube256(r, g, b)
|
||||
fmt.Print(block256(idx, 3))
|
||||
}
|
||||
fmt.Print(" ") // Space between green columns
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func showGrayscaleRamp() {
|
||||
fmt.Println("── Part 1: Grayscale Ramp ──")
|
||||
fmt.Println("Displaying the 24-step grayscale ramp (Indices 232-255).")
|
||||
|
||||
for step := uint8(0); step < 24; step++ {
|
||||
idx := color.Gray256(step)
|
||||
fmt.Print(block256(idx, 2))
|
||||
}
|
||||
fmt.Println("\n")
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// PART 2: Naked Terminal (No Desktop Environment)
|
||||
// =====================================================================
|
||||
|
||||
func showNakedTerminalDegradation() {
|
||||
fmt.Println("── Part 2: Naked Terminal (Quantization Fallback) ──")
|
||||
fmt.Println("Simulating how 24-bit Truecolor gracefully degrades in environments")
|
||||
fmt.Println("without a modern desktop compositor (e.g., bare TTY, tmux, legacy SSH).")
|
||||
fmt.Println()
|
||||
|
||||
// 1. Force the lazy evaluation of the 262KB Redmean LUT.
|
||||
// This makes RGBTo256 O(1) during the render loop.
|
||||
color.WarmXterm256()
|
||||
|
||||
// 2. Render a smooth gradient in both truecolor and degraded 256-color
|
||||
const steps = 40
|
||||
start, end := color.ElectricViolet, color.LimeGreen
|
||||
|
||||
var truecolor strings.Builder
|
||||
var naked256 strings.Builder
|
||||
|
||||
for i := 0; i < steps; i++ {
|
||||
t := float64(i) / float64(steps-1)
|
||||
c := color.Lerp(start, end, t)
|
||||
|
||||
// Desktop Environment (24-bit)
|
||||
truecolor.WriteString(blockRGB(c, 2))
|
||||
|
||||
// Naked Terminal (8-bit Quantized via Redmean perceptual distance)
|
||||
idx := color.RGBTo256(c)
|
||||
naked256.WriteString(block256(idx, 2))
|
||||
}
|
||||
|
||||
fmt.Println("Desktop Environment (24-bit smooth interpolation):")
|
||||
fmt.Println(truecolor.String())
|
||||
fmt.Println("Naked Terminal (8-bit perceptual quantization via RGBTo256):")
|
||||
fmt.Println(naked256.String())
|
||||
fmt.Println()
|
||||
|
||||
// 3. Show exact index mapping for specific named colors
|
||||
fmt.Println("Nearest xterm-256 mappings for Named RGB colors:")
|
||||
named := []struct {
|
||||
name string
|
||||
c color.RGB
|
||||
}{
|
||||
{"HotPink", color.HotPink},
|
||||
{"BurntOrange", color.BurntOrange},
|
||||
{"MintGreen", color.MintGreen},
|
||||
{"DeepNavy", color.DeepNavy},
|
||||
{"SlateGray", color.SlateGray},
|
||||
}
|
||||
|
||||
for _, n := range named {
|
||||
// Calculate nearest palette index
|
||||
idx := color.RGBTo256(n.c)
|
||||
|
||||
// Map index back to mathematical coordinates to see where it landed
|
||||
cubeStr := ""
|
||||
if idx >= 16 && idx <= 231 {
|
||||
r, g, b := color.CubeRGB256(idx)
|
||||
cubeStr = fmt.Sprintf("Cube(r:%d, g:%d, b:%d)", r, g, b)
|
||||
} else if idx >= 232 {
|
||||
cubeStr = fmt.Sprintf("GrayStep(%d)", idx-232)
|
||||
} else {
|
||||
cubeStr = "System(0-15)"
|
||||
}
|
||||
|
||||
fmt.Printf(" %-12s %s (Truecolor) -> %s (Index %3d) %s\n",
|
||||
n.name,
|
||||
blockRGB(n.c, 4),
|
||||
block256(idx, 4),
|
||||
idx,
|
||||
cubeStr,
|
||||
)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func main() {
|
||||
// Execute Part 1
|
||||
show256Cube()
|
||||
showGrayscaleRamp()
|
||||
|
||||
// Execute Part 2
|
||||
showNakedTerminalDegradation()
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
stdcolor "image/color"
|
||||
"math"
|
||||
"os"
|
||||
"os/signal"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/lixenwraith/color"
|
||||
)
|
||||
|
||||
const reset = "\x1b[0m"
|
||||
|
||||
// bg returns the SGR sequence for a 24-bit background.
|
||||
func bg(c color.RGB) string { return fmt.Sprintf("\x1b[48;2;%d;%d;%dm", c.R, c.G, c.B) }
|
||||
|
||||
// fg returns the SGR sequence for a 24-bit foreground.
|
||||
func fg(c color.RGB) string { return fmt.Sprintf("\x1b[38;2;%d;%d;%dm", c.R, c.G, c.B) }
|
||||
|
||||
// block renders n background-colored spaces as a swatch.
|
||||
func block(c color.RGB, n int) string { return bg(c) + strings.Repeat(" ", n) + reset }
|
||||
|
||||
func showPalette() {
|
||||
fmt.Println("── Named palette ──")
|
||||
entries := []struct {
|
||||
name string
|
||||
c color.RGB
|
||||
}{
|
||||
{"Black", color.Black}, {"White", color.White}, {"Red", color.Red},
|
||||
{"Orange", color.Orange}, {"Gold", color.Gold}, {"Yellow", color.Yellow},
|
||||
{"Lime", color.Lime}, {"ForestGreen", color.ForestGreen}, {"Teal", color.Teal},
|
||||
{"Cyan", color.Cyan}, {"RoyalBlue", color.RoyalBlue}, {"Blue", color.Blue},
|
||||
{"Magenta", color.Magenta}, {"HotPink", color.HotPink}, {"Coral", color.Coral},
|
||||
{"Silver", color.Silver},
|
||||
}
|
||||
for _, e := range entries {
|
||||
fmt.Printf("%s %-12s %s\n", block(e.c, 4), e.name, e.c.Hex())
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func showBlendModes() {
|
||||
fmt.Println("── Blend modes (dst=NavyBlue, src=Orange, alpha=0.6) ──")
|
||||
dst, src := color.NavyBlue, color.Orange
|
||||
const a = 0.6
|
||||
fmt.Printf("%s dst %s src\n", block(dst, 6), block(src, 6))
|
||||
modes := []struct {
|
||||
name string
|
||||
out color.RGB
|
||||
}{
|
||||
{"Blend", color.Blend(dst, src, a)},
|
||||
{"Add", color.Add(dst, src, a)},
|
||||
{"Screen", color.Screen(dst, src, a)},
|
||||
{"Overlay", color.Overlay(dst, src, a)},
|
||||
{"SoftLight", color.SoftLight(dst, src, a)},
|
||||
{"Max", color.Max(dst, src, a)},
|
||||
}
|
||||
for _, m := range modes {
|
||||
fmt.Printf("%s %-10s %s\n", block(m.out, 6), m.name, m.out.Hex())
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func showGradient() {
|
||||
fmt.Println("── Lerp gradient: DarkCrimson → Gold → Teal ──")
|
||||
stops := []color.RGB{color.DarkCrimson, color.Gold, color.Teal}
|
||||
const steps = 36
|
||||
var line strings.Builder
|
||||
for i := 0; i < steps; i++ {
|
||||
seg := (float64(i) / float64(steps-1)) * float64(len(stops)-1)
|
||||
idx := int(seg)
|
||||
if idx >= len(stops)-1 {
|
||||
idx = len(stops) - 2
|
||||
}
|
||||
line.WriteString(block(color.Lerp(stops[idx], stops[idx+1], seg-float64(idx)), 1))
|
||||
}
|
||||
fmt.Println(line.String())
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func showGrayscale() {
|
||||
fmt.Println("── Grayscale, Desaturate, Luma ──")
|
||||
base := color.Coral
|
||||
fmt.Printf("%s base %s luma=%d\n", block(base, 6), base.Hex(), color.Luma(base))
|
||||
fmt.Printf("%s grayscale %s\n", block(color.Grayscale(base), 6), color.Grayscale(base).Hex())
|
||||
var line strings.Builder
|
||||
for i := 0; i <= 8; i++ {
|
||||
line.WriteString(block(color.Desaturate(base, float64(i)/8.0), 3))
|
||||
}
|
||||
fmt.Printf("%s desaturate t=0→1\n\n", line.String())
|
||||
}
|
||||
|
||||
func showHex() {
|
||||
fmt.Println("── Hex parse / format ──")
|
||||
for _, s := range []string{"#ff8800", "0f0", "#1a1b26", "bad!"} {
|
||||
c, err := color.ParseHex(s)
|
||||
if err != nil {
|
||||
fmt.Printf(" %-8q → error: %v\n", s, err)
|
||||
continue
|
||||
}
|
||||
fmt.Printf(" %-8q → %s %s%s%s\n", s, block(c, 3), fg(c), c.Hex(), reset)
|
||||
}
|
||||
c := color.MustParseHex("#41c7c7")
|
||||
fmt.Printf(" MustParseHex(#41c7c7) → %s %s\n\n", block(c, 3), c.Hex())
|
||||
}
|
||||
|
||||
func showNearest() {
|
||||
fmt.Println("── Nearest palette color (RedmeanDistance) ──")
|
||||
named := []struct {
|
||||
name string
|
||||
c color.RGB
|
||||
}{
|
||||
{"Red", color.Red}, {"Orange", color.Orange}, {"Gold", color.Gold},
|
||||
{"Lime", color.Lime}, {"Teal", color.Teal}, {"Blue", color.Blue},
|
||||
{"Magenta", color.Magenta}, {"White", color.White}, {"Black", color.Black},
|
||||
}
|
||||
for _, hex := range []string{"#ff5522", "#118ab2", "#2b2b2b"} {
|
||||
target := color.MustParseHex(hex)
|
||||
best, bestD := named[0], color.RedmeanDistance(target, named[0].c)
|
||||
for _, n := range named[1:] {
|
||||
if d := color.RedmeanDistance(target, n.c); d < bestD {
|
||||
best, bestD = n, d
|
||||
}
|
||||
}
|
||||
fmt.Printf(" %s %s ≈ %s %-8s %s\n", block(target, 3), hex, block(best.c, 3), best.name, best.c.Hex())
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
func showLerpFixed() {
|
||||
fmt.Println("── LerpFixed (Q16.16 fixed-point) ──")
|
||||
const shift = 16
|
||||
a, b := color.RoyalBlue, color.Gold
|
||||
var line strings.Builder
|
||||
for i := 0; i <= 16; i++ {
|
||||
t := int64(i) << (shift - 4) // i/16 expressed in Q16.16; spans [0, 1<<shift]
|
||||
line.WriteString(block(color.LerpFixed(a, b, t, shift), 3))
|
||||
}
|
||||
fmt.Printf("%s RoyalBlue → Gold\n\n", line.String())
|
||||
}
|
||||
|
||||
func showImageBridge() {
|
||||
fmt.Println("── image/color bridge (From / RGBA) ──")
|
||||
// image/color values are alpha-premultiplied; From un-premultiplies and drops alpha.
|
||||
opaque := stdcolor.RGBA{R: 200, G: 100, B: 50, A: 255}
|
||||
c := color.From(opaque)
|
||||
fmt.Printf(" From(RGBA{200,100,50,255}) = %s %s\n", block(c, 3), c.Hex())
|
||||
|
||||
half := stdcolor.RGBA{R: 100, G: 50, B: 25, A: 128} // 50%% alpha, premultiplied
|
||||
c2 := color.From(half)
|
||||
fmt.Printf(" From(50%% alpha) = %s %s (recovers base hue)\n", block(c2, 3), c2.Hex())
|
||||
|
||||
var _ stdcolor.Color = color.Orange // RGB satisfies image/color.Color
|
||||
r, g, b, alpha := color.Orange.RGBA()
|
||||
fmt.Printf(" color.Orange.RGBA() = (%d,%d,%d,%d)\n\n", r, g, b, alpha)
|
||||
}
|
||||
|
||||
// showPulse animates a Blend between two colors on one line until ctx is cancelled.
|
||||
func showPulse(ctx context.Context) {
|
||||
const width = 32
|
||||
fmt.Println("── Live pulse (Ctrl+C to exit) ──")
|
||||
base, glow := color.DodgerBlue, color.Gold
|
||||
ticker := time.NewTicker(60 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
phase := 0.0
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
fmt.Print("\r" + strings.Repeat(" ", width+10) + "\r")
|
||||
fmt.Println("interrupted — bye")
|
||||
return
|
||||
case <-ticker.C:
|
||||
phase += 0.15
|
||||
t := (math.Sin(phase) + 1) / 2 // 0..1
|
||||
fmt.Printf("\r%s t=%.2f", block(color.Blend(base, glow, t), width), t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt)
|
||||
defer stop()
|
||||
|
||||
showPalette()
|
||||
showBlendModes()
|
||||
showGradient()
|
||||
showGrayscale()
|
||||
showHex()
|
||||
showNearest()
|
||||
showLerpFixed()
|
||||
showImageBridge()
|
||||
showPulse(ctx)
|
||||
}
|
||||
Reference in New Issue
Block a user