From 5b60717b8f38a13c5256207d2b7d1f18785dc0a8 Mon Sep 17 00:00:00 2001 From: "sashakoshka@tebibyte.media" Date: Sat, 22 Jun 2024 15:37:20 -0400 Subject: [PATCH] Add an HSVA color implementation --- internal/color.go | 106 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 internal/color.go diff --git a/internal/color.go b/internal/color.go new file mode 100644 index 0000000..e728cdf --- /dev/null +++ b/internal/color.go @@ -0,0 +1,106 @@ +package internal + +type HSVA struct { + H float64 + S float64 + V float64 + A uint8 +} + +func (hsva HSVA) RGBA () (r, g, b, a uint32) { + // Adapted from: + // https://www.cs.rit.edu/~ncs/color/t_convert.html + + component := func (x float64) uint32 { + return uint32(float64(0xFFFF) * x) + } + + ca := uint32(hsva.A) << 8 + s := clamp01(hsva.S) + v := clamp01(hsva.V) + if s == 0 { + light := component(v) + return light, light, light, ca + } + + h := clamp01(hsva.H) * 360 + sector := int(h / 60) + offset := (h / 60) - float64(sector) + + fac := float64(hsva.A) / 255 + p := component(fac * v * (1 - s)) + q := component(fac * v * (1 - s * offset)) + t := component(fac * v * (1 - s * (1 - offset))) + va := component(v) + + switch sector { + case 0: return va, t, p, ca + case 1: return q, va, p, ca + case 2: return p, va, t, ca + case 3: return p, q, va, ca + case 4: return t, p, va, ca + default: return va, p, q, ca + } +} + +// Canon returns the color but with the H, S, and V fields are constrained to +// the range 0.0-1.0 +func (hsva HSVA) Canon () HSVA { + hsva.H = clamp01(hsva.H) + hsva.S = clamp01(hsva.S) + hsva.V = clamp01(hsva.V) + return hsva +} + +func clamp01 (x float64) float64 { + if x > 1.0 { return 1.0 } + if x < 0.0 { return 0.0 } + return x +} + +func RGBAToHSVA (r, g, b, a uint32) HSVA { + // Adapted from: + // https://www.cs.rit.edu/~ncs/color/t_convert.html + + // FIXME: this does not always work! + + component := func (x uint32) float64 { + return clamp01(float64(x) / 0xFFFF) + } + cr := component(r) + cg := component(g) + cb := component(b) + + var maxComponent float64 + if cr > maxComponent { maxComponent = cr } + if cg > maxComponent { maxComponent = cg } + if cb > maxComponent { maxComponent = cb } + var minComponent = 1.0 + if cr < minComponent { minComponent = cr } + if cg < minComponent { minComponent = cg } + if cb < minComponent { minComponent = cb } + + hsva := HSVA { + V: maxComponent, + A: uint8(a >> 8), + } + + delta := maxComponent - minComponent + if maxComponent == 0 { + // hsva.S is undefined, so hue doesn't matter + return hsva + } + hsva.S = delta / maxComponent + + switch { + case cr == maxComponent: hsva.H = (cg - cb) / delta + case cg == maxComponent: hsva.H = 2 + (cb - cr) / delta + case cb == maxComponent: hsva.H = 4 + (cr - cg) / delta + } + + hsva.H *= 60 + if hsva.H < 0 { hsva.H += 360 } + hsva.H /= 360 + + return hsva +}