# color 24-bit RGB values, perceptual metrics, and blend operations. No output device, no dependencies. Extracted from [lixenwraith/terminal](https://github.com/lixenwraith/terminal) so renderers — terminal, GUI, image, framebuffer — share one color type and one set of operations without linking terminal I/O, `x/sys`, or termios. ## Install ``` go get github.com/lixenwraith/color ``` Go 1.26+. Standard library only. ## Type ```go type RGB struct { R uint8 `toml:"r"` G uint8 `toml:"g"` B uint8 `toml:"b"` } ``` Three bytes, comparable, pointer-free: safe to embed in dense cell or pixel buffers and to bind directly from config. `RGB` implements `image/color.Color`, so values pass into `image`, `draw`, and GUI toolkit pipelines unchanged. `From` converts back, un-premultiplying alpha and discarding it. ```go img.Set(x, y, color.Amber) // RGB satisfies image/color.Color c := color.From(img.At(x, y)) // back to RGB ``` Where both packages are needed at one site, alias the standard library: `import stdcolor "image/color"`. ## Operations | Call | Behavior | | --- | --- | | `Blend(dst, src, alpha)` | Linear alpha compositing | | `SoftLight(dst, src, intensity)` | Perez soft light; gentler than linear alpha | | `Overlay(dst, src, alpha)` | Multiply on darks, screen on lights | | `Screen(dst, src, alpha)` | Always lightens; glow accumulation without `Add` clipping | | `Add(dst, src, alpha)` | Saturating additive | | `Max(dst, src, alpha)` | Per-channel maximum | | `Scale(c, factor)` | Channel multiply, saturating | | `Grayscale(c)`, `Luma(c)` | Rec. 601 luma | | `c.Lerp(other, t)` | Linear interpolation | | `RedmeanDistance(a, b)` | Squared perceptual distance, for nearest-color search | All operations are pure. `alpha` and `t` clamp to `[0,1]`; channels saturate. Integer paths avoid division; soft light is table-driven, no `sqrt` per channel. ```go bg := color.Obsidian glow := color.Screen(bg, color.Amber, 0.4) edge := bg.Lerp(color.Amber, 0.75) warm := color.SoftLight(edge, color.Terracotta, 0.3) ``` ## Palette ~120 named colors, grouped by hue and ordered dark-to-light: `Obsidian`, `Amber`, `EmeraldGreen`, `LightSkyBlue`, `Vermilion`, … Standard names (CSS, X11) where the RGB matches; descriptive compounds otherwise. Package-level `var`s. **Read-only by contract** — the language permits assignment, the package does not. Alias them into domain parameter files rather than mutating them. ## Hex ```go c, err := color.ParseHex("#4a90d9") // also "4a90d9", "#abc", "abc" s := c.Hex() // "#4a90d9" var Accent = color.MustParseHex("#ff8800") ``` `RGB` deliberately does **not** implement `encoding.TextUnmarshaler`. TOML and JSON decoders prefer it over struct-field unification, which would silently break table-form config: ```toml accent = { r = 255, g = 136, b = 0 } ``` ## With terminal `terminal.Cell` carries `color.RGB` directly. Quantization stays device-side: ```go idx := terminal.RGBTo256(color.EmeraldGreen) // xterm-256 index ``` 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. ## Concurrency Values are immutable, operations are pure, lookup tables are built at package init. Safe for concurrent use. ## License See `LICENSE`.