Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

color.gno

3.03 Kb · 121 lines
  1package art
  2
  3import "strings"
  4
  5// Default is the colour that means "let the consumer decide": the terminal's
  6// own foreground or background in ANSI, and transparent in SVG.
  7const Default = -1
  8
  9// RGB packs three 0-255 components into the 0xRRGGBB form this package uses.
 10// Components outside 0-255 are clamped rather than wrapped, so a computed
 11// channel cannot silently alias onto a neighbouring one.
 12func RGB(r, g, b int) int {
 13	return clamp8(r)<<16 | clamp8(g)<<8 | clamp8(b)
 14}
 15
 16// Split returns the three 0-255 components of a packed colour. Default splits
 17// to zeroes, so callers must test for Default before calling it rather than
 18// after.
 19func Split(c int) (r, g, b int) {
 20	if c < 0 {
 21		return 0, 0, 0
 22	}
 23	return (c >> 16) & 0xff, (c >> 8) & 0xff, c & 0xff
 24}
 25
 26// ParseHex reads "#rrggbb", "#rgb", or either without the leading '#', and
 27// reports whether it understood the string. An empty string is not an error but
 28// it is not a colour either: it reports Default, false, which is how a palette
 29// marks a transparent entry.
 30func ParseHex(s string) (int, bool) {
 31	s = strings.TrimSpace(s)
 32	s = strings.TrimPrefix(s, "#")
 33	switch len(s) {
 34	case 3:
 35		r, ok1 := hexDigit(s[0])
 36		g, ok2 := hexDigit(s[1])
 37		b, ok3 := hexDigit(s[2])
 38		if !ok1 || !ok2 || !ok3 {
 39			return Default, false
 40		}
 41		// #abc is #aabbcc, not #0a0b0c.
 42		return RGB(r*17, g*17, b*17), true
 43	case 6:
 44		r, ok1 := hexByte(s[0], s[1])
 45		g, ok2 := hexByte(s[2], s[3])
 46		b, ok3 := hexByte(s[4], s[5])
 47		if !ok1 || !ok2 || !ok3 {
 48			return Default, false
 49		}
 50		return RGB(r, g, b), true
 51	}
 52	return Default, false
 53}
 54
 55// Hex is the inverse of ParseHex. Default renders as the empty string, because
 56// there is no hex spelling of "the consumer's default".
 57func Hex(c int) string {
 58	if c < 0 {
 59		return ""
 60	}
 61	r, g, b := Split(c)
 62	var sb strings.Builder
 63	sb.WriteByte('#')
 64	writeHexByte(&sb, r)
 65	writeHexByte(&sb, g)
 66	writeHexByte(&sb, b)
 67	return sb.String()
 68}
 69
 70// Luminance returns perceived brightness on 0-255, using the Rec. 709 weights
 71// (0.2126 R, 0.7152 G, 0.0722 B) in integer arithmetic. Default is treated as
 72// black, so an unset colour ramps to the darkest glyph rather than to a
 73// midpoint that would read as real content.
 74//
 75// This is the function [Ramp] keys on, and the package doc explains why keying
 76// on it is the wrong default for a sprite.
 77func Luminance(c int) int {
 78	if c < 0 {
 79		return 0
 80	}
 81	r, g, b := Split(c)
 82	return (2126*r + 7152*g + 722*b) / 10000
 83}
 84
 85func clamp8(v int) int {
 86	if v < 0 {
 87		return 0
 88	}
 89	if v > 255 {
 90		return 255
 91	}
 92	return v
 93}
 94
 95func hexDigit(c byte) (int, bool) {
 96	switch {
 97	case c >= '0' && c <= '9':
 98		return int(c - '0'), true
 99	case c >= 'a' && c <= 'f':
100		return int(c-'a') + 10, true
101	case c >= 'A' && c <= 'F':
102		return int(c-'A') + 10, true
103	}
104	return 0, false
105}
106
107func hexByte(hi, lo byte) (int, bool) {
108	h, ok1 := hexDigit(hi)
109	l, ok2 := hexDigit(lo)
110	if !ok1 || !ok2 {
111		return 0, false
112	}
113	return h*16 + l, true
114}
115
116const hexDigits = "0123456789abcdef"
117
118func writeHexByte(sb *strings.Builder, v int) {
119	sb.WriteByte(hexDigits[(v>>4)&0xf])
120	sb.WriteByte(hexDigits[v&0xf])
121}