3.3 KiB
color
24-bit RGB values, perceptual metrics, and blend operations. No output device, no dependencies.
Extracted from 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
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.
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.
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 vars. Read-only by contract — the language permits assignment, the package does not. Alias them into domain parameter files rather than mutating them.
Hex
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:
accent = { r = 255, g = 136, b = 0 }
With terminal
terminal.Cell carries color.RGB directly. Quantization stays device-side:
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.