Skip to content

Instantly share code, notes, and snippets.

@jessestricker
Created September 21, 2022 17:50
Show Gist options
  • Save jessestricker/ad72f6fb7d416602cc8d65474500c670 to your computer and use it in GitHub Desktop.
Save jessestricker/ad72f6fb7d416602cc8d65474500c670 to your computer and use it in GitHub Desktop.
Hexe Color Model
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))
}
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