Created
September 21, 2022 17:50
-
-
Save jessestricker/ad72f6fb7d416602cc8d65474500c670 to your computer and use it in GitHub Desktop.
Hexe Color Model
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package hexe | |
import ( | |
"math" | |
hsluv "github.com/hsluv/hsluv-go" | |
) | |
// Color defines an 8-bit packed color value in the HSLuv color space. | |
type Color uint8 | |
// HSL defines a color value in the HSLuv color space. | |
type HSL struct { | |
H float64 // hue [0, 360) | |
S float64 // saturation [0, 100] | |
L float64 // lightness [0, 100] | |
} | |
// RGB defines a color value in the sRGB color space. | |
type RGB struct { | |
R uint8 // red [0, 256) | |
G uint8 // green [0, 256) | |
B uint8 // blue [0, 256) | |
} | |
// decoding constants | |
const ( | |
hueOff = 12.177050630061776 // this is the H value of #ff0000 converted to HSL | |
satMin, satMax = 30.0, 90.0 // of [0, 100] | |
lgtMin, lgtMax = 25.0, 80.0 // of [0, 100] | |
) | |
func (c Color) Decode() HSL { | |
v := uint(c) | |
// non-color shades | |
if v < 16 { | |
return HSL{0, 0, stepValue(v, 16, 0, 100)} | |
} | |
// saturated colors | |
// binary layout: HHHH.SSLL | |
v -= 16 | |
// get indexes | |
hi := v >> 4 | |
si := (v >> 2) & 3 | |
li := v & 3 | |
// convert to values | |
return HSL{ | |
stepValue(hi, 16, 0, 360) + hueOff, | |
stepValue(si, 4, satMin, satMax), | |
stepValue(li, 4, lgtMin, lgtMax)} | |
} | |
func (c Color) DecodeRGB() RGB { | |
return HSLtoRGB(c.Decode()) | |
} | |
func HSLtoRGB(hsl HSL) RGB { | |
r, g, b := hsluv.HsluvToRGB(hsl.H, hsl.S, hsl.L) | |
f := func(v float64) uint8 { | |
v = math.Round(clamp(v, 0, 1) * 255) | |
return uint8(v) | |
} | |
return RGB{f(r), f(g), f(b)} | |
} | |
// stepValue transforms an index to a value in [min, max]. | |
// | |
// Let there be the set V, which contains n values from [min, max], | |
// where the difference between all values is equal. The returned value is | |
// the element of the set V with the index of i. | |
// | |
// Requirements: n > 0 && i < n && max >= min | |
// If the requirements are violated, this function panics. | |
// | |
func stepValue(i, n uint, min, max float64) float64 { | |
if n == 0 || i == n || min > max { | |
panic("domain requirements are violated") | |
} | |
step := ((max - min) / float64(n-1)) | |
return float64(i)*step + min | |
} | |
func clamp(v, min, max float64) float64 { | |
return math.Min(max, math.Max(min, v)) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"errors" | |
"flag" | |
"fmt" | |
"strings" | |
"./hexe" | |
) | |
type format string | |
const ( | |
csv format = "csv" | |
json format = "json" | |
markdown format = "md" | |
html format = "html" | |
cpp format = "c++" | |
) | |
func (f format) String() string { return string(f) } | |
func (f *format) Set(str string) error { | |
nf := format(str) | |
var ok bool | |
if ok, *f = nf.IsValid(); ok { | |
return nil | |
} else { | |
return errors.New("must be “csv”, “json”, “md”, “html”, or “c++”") | |
} | |
} | |
func (f format) IsValid() (bool, format) { | |
if strings.EqualFold(f.String(), csv.String()) { | |
return true, csv | |
} | |
if strings.EqualFold(f.String(), json.String()) { | |
return true, json | |
} | |
if strings.EqualFold(f.String(), markdown.String()) { | |
return true, markdown | |
} | |
if strings.EqualFold(f.String(), html.String()) { | |
return true, html | |
} | |
if strings.EqualFold(f.String(), cpp.String()) { | |
return true, cpp | |
} | |
return false, f | |
} | |
var outputFormat = csv | |
func main() { | |
// setup up and parse flags | |
flag.Var(&outputFormat, "format", "The format of the output: “csv”, “json”, “md”, “html”, or “c++”.") | |
flag.Parse() | |
// generate palette | |
p := newPalette() | |
// print palette | |
if outputFormat == csv { | |
printCSV(p) | |
} else if outputFormat == markdown { | |
printMarkdown(p) | |
} else if outputFormat == json { | |
printJson(p) | |
} else if outputFormat == html { | |
printHTML(p) | |
} else { | |
printCPP(p) | |
} | |
} | |
type color struct { | |
hsl hexe.HSL | |
rgb hexe.RGB | |
} | |
type palette [256]color | |
func newPalette() palette { | |
var p palette | |
for i := 0; i < len(p); i++ { | |
hsl := hexe.Color(i).Decode() | |
rgb := hexe.HSLtoRGB(hsl) | |
p[i] = color{hsl, rgb} | |
} | |
return p | |
} | |
func printCSV(p palette) { | |
fmt.Println("Index, H, S, L, R, G, B") | |
for i, c := range p { | |
fmt.Printf("%v, %v, %v, %v, %v, %v, %v\n", i, c.hsl.H, c.hsl.S, c.hsl.L, c.rgb.R, c.rgb.G, c.rgb.B) | |
} | |
} | |
func printJson(p palette) { | |
fmt.Println("[") | |
for i, c := range p { | |
fmt.Print(" {") | |
fmt.Printf("hsl: [%.2f, %.2f, %.2f]", c.hsl.H, c.hsl.S, c.hsl.L) | |
fmt.Print(", ") | |
fmt.Printf("rgb: [%v, %v, %v]", c.rgb.R, c.rgb.G, c.rgb.B) | |
fmt.Print("}") | |
if i < 255 { | |
fmt.Print(",") | |
} | |
fmt.Println() | |
} | |
fmt.Println("]") | |
} | |
func printMarkdown(p palette) { | |
panic("not implemented yet") | |
fmt.Println("# Hexe Color Palette") | |
fmt.Println("<table>") | |
fmt.Println(" <tr><th>Index</th><th colspan='3'>HSL</th><th colspan='3'>RGB</th><th>Swatch</th></tr>") | |
for i, c := range p { | |
fmt.Println(" <tr>") | |
fmt.Printf(" <td>%v</td>\n", i) // index | |
fmt.Printf(" <td>%v °</td><td>%.1f %%</td><td>%.1f %%</td>\n", c.hsl.H, c.hsl.S, c.hsl.L) // HSL | |
fmt.Printf(" <td>%v</td><td>%v</td><td>%v</td>\n", c.rgb.R, c.rgb.G, c.rgb.B) // RGB | |
fmt.Printf(" <td style='background: rgb(%v,%v,%v)'></td>\n", c.rgb.R, c.rgb.G, c.rgb.B) // Swatch | |
fmt.Println(" </tr>") | |
} | |
fmt.Println("</table>") | |
} | |
func printHTML(p palette) { | |
fmt.Print(` | |
<html><head> | |
<title>Hexe Color Palette</title> | |
<style> | |
body { font: 16px/1.25 sans-serif; max-width: 40em; margin: 0 auto; padding: 1rem } | |
table { border-collapse: collapse } | |
th { border-bottom: 2px solid black } | |
td { text-align: right } | |
td:nth-child(1), td:nth-child(4) { border-right: 1px solid grey } | |
</style> | |
</head><body> | |
<h1>Hexe Color Palette</h1> | |
<table> | |
<tr><th>Index</th><th colspan='3'>HSL</th><th colspan='3'>RGB</th><th>Swatch</th></tr>`) | |
for i, c := range p { | |
fmt.Println(" <tr>") | |
fmt.Printf(" <td>%v</td>\n", i) // index | |
fmt.Printf(" <td>%.2f °</td><td>%.2f %%</td><td>%.2f %%</td>\n", c.hsl.H, c.hsl.S, c.hsl.L) // HSL | |
fmt.Printf(" <td>%v</td><td>%v</td><td>%v</td>\n", c.rgb.R, c.rgb.G, c.rgb.B) // RGB | |
fmt.Printf(" <td style='background: rgb(%v,%v,%v)'></td>\n", c.rgb.R, c.rgb.G, c.rgb.B) // Swatch | |
fmt.Println(" </tr>") | |
} | |
fmt.Println("</table></body></html>") | |
} | |
func printCPP(p palette) { | |
panic("not implemented yet") | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment