package internal import "image/color" // HSV represents a color with hue, saturation, and value components. Each // component C is in range 0 <= C <= 1. type HSV struct { H float64 S float64 V float64 } // HSVA is an HSV color with an added 8-bit alpha component. The alpha component // ranges from 0x0000 (fully transparent) to 0xFFFF (opaque), and has no bearing // on the other components. type HSVA struct { H float64 S float64 V float64 A uint16 } var ( HSVModel color.Model = color.ModelFunc(hsvModel) HSVAModel color.Model = color.ModelFunc(hsvaModel) ) func (hsv HSV) 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) } s := clamp01(hsv.S) v := clamp01(hsv.V) if s == 0 { light := component(v) return light, light, light, 0xFFFF } h := clamp01(hsv.H) * 360 sector := int(h / 60) // otherwise when given 1.0 for H, sector would overflow to 6 if sector > 5 { sector = 5 } offset := (h / 60) - float64(sector) p := component(v * (1 - s)) q := component(v * (1 - s * offset)) t := component(v * (1 - s * (1 - offset))) va := component(v) switch sector { case 0: return va, t, p, 0xFFFF case 1: return q, va, p, 0xFFFF case 2: return p, va, t, 0xFFFF case 3: return p, q, va, 0xFFFF case 4: return t, p, va, 0xFFFF default: return va, p, q, 0xFFFF } } func (hsva HSVA) RGBA () (r, g, b, a uint32) { r, g, b, a = HSV { H: hsva.H, S: hsva.S, V: hsva.V, }.RGBA() a = uint32(hsva.A) // alpha premultiplication r = (r * a) / 0xFFFF g = (g * a) / 0xFFFF b = (b * a) / 0xFFFF return } // Canon returns the color but with the H, S, and V fields are constrained to // the range 0.0-1.0 func (hsv HSV) Canon () HSV { hsv.H = clamp01(hsv.H) hsv.S = clamp01(hsv.S) hsv.V = clamp01(hsv.V) return hsv } // 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 hsvModel (c color.Color) color.Color { switch c := c.(type) { case HSV: return c case HSVA: return HSV { H: c.H, S: c.S, V: c.V } default: r, g, b, a := c.RGBA() // alpha unpremultiplication r = (r / a) * 0xFFFF g = (g / a) * 0xFFFF b = (b / a) * 0xFFFF return rgbToHSV(r, g, b) } } func hsvaModel (c color.Color) color.Color { switch c := c.(type) { case HSV: return HSVA { H: c.H, S: c.S, V: c.V, A: 0xFFFF } case HSVA: return c default: r, g, b, a := c.RGBA() hsv := rgbToHSV(r, g, b) return HSVA { H: hsv.H, S: hsv.S, V: hsv.V, A: uint16(a), } } } func rgbToHSV (r, g, b uint32) HSV { // Adapted from: // https://www.cs.rit.edu/~ncs/color/t_convert.html 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 } hsv := HSV { V: maxComponent, } delta := maxComponent - minComponent if delta == 0 { // hsva.S is undefined, so hue doesn't matter return hsv } hsv.S = delta / maxComponent switch { case cr == maxComponent: hsv.H = (cg - cb) / delta case cg == maxComponent: hsv.H = 2 + (cb - cr) / delta case cb == maxComponent: hsv.H = 4 + (cr - cg) / delta } hsv.H *= 60 if hsv.H < 0 { hsv.H += 360 } hsv.H /= 360 return hsv }