-
Notifications
You must be signed in to change notification settings - Fork 1
/
hsv.go
137 lines (106 loc) · 3.47 KB
/
hsv.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
package colorx
import (
"image/color"
"math"
"github.com/somebadcode/go-colorx/v2/internal/mathx"
)
// HSVA is an implementation of the HSV (Hue, Saturation and Value) color model. HSV is also known as HSB (Hue,
// Saturation, Brightness).
type HSVA struct {
H float64 // Hue ∈ [0, 360)
S float64 // Saturation ∈ [0, 1]
V float64 // Value/Brightness ∈ [0, 1]
A float64 // Alpha ∈ [0, 1]
}
// HSVAModel can convert the color to the HSVA color model defined in this package.
var HSVAModel = color.ModelFunc(hsvaModel)
func hsvaModel(c color.Color) color.Color {
if _, ok := c.(HSVA); ok {
return c
}
r, g, b, a := c.RGBA()
h, s, v, ha := RGBAToHSVA(uint8(r>>8), uint8(g>>8), uint8(b>>8), uint8(a>>8))
return HSVA{
H: h,
S: s,
V: v,
A: ha,
}
}
// RGBAToHSVA converts RGBA to Hue, Saturation, Value and Alpha.
func RGBAToHSVA(r, g, b, a uint8) (float64, float64, float64, float64) {
var hue, saturation, value, alpha float64
// Convert R, G and B to floats.
red := float64(r) / math.MaxUint8
green := float64(g) / math.MaxUint8
blue := float64(b) / math.MaxUint8
alpha = float64(a) / math.MaxUint8
// Get the most and least dominant colors.
cMax := math.Max(red, math.Max(green, blue))
cMin := math.Min(red, math.Min(green, blue))
// Value is the value of the dominant color.
value = cMax
// Get color delta.
delta := cMax - cMin
// Saturation is derived from the delta, but it's zero if cMax is zero (saturation is initialized as zero).
if cMax != 0.0 {
saturation = delta / cMax
}
// Hue is derived from the dominant color.
switch cMax {
case cMin: // delta == 0
hue = 0.0
case red:
hue = math.FMA(60.0, math.Mod((green-blue)/delta, 6), 360.0)
case green:
hue = math.FMA(60.0, (blue-red)/delta+2, 360.0)
case blue:
hue = math.FMA(60.0, (red-green)/delta+4, 360.0)
}
hue = math.Mod(hue, 360.0)
return hue, saturation, value, alpha
}
// RGBA returns the alpha-premultiplied red, green, blue and alpha values for the color.
func (hsva HSVA) RGBA() (r, g, b, a uint32) {
var rgba color.RGBA
rgba.A = uint8(hsva.A * math.MaxUint8)
if mathx.Equal(hsva.S, 0.0) {
rgba.R = uint8(hsva.V * math.MaxUint8)
rgba.G = uint8(hsva.V * math.MaxUint8)
rgba.B = uint8(hsva.V * math.MaxUint8)
return rgba.RGBA()
}
angle := math.Mod(hsva.H+360.0, 360.0)
// sextant will be the sextant of the dominant color.
sextant, frac := math.Modf(angle / 60.0)
p := hsva.V * (1.0 - hsva.S)
q := hsva.V * (1.0 - (hsva.S * frac))
t := hsva.V * (1.0 - (hsva.S * (1.0 - frac)))
switch sextant {
case 0:
rgba.R = uint8(math.Floor(hsva.V * math.MaxUint8))
rgba.G = uint8(math.Floor(t * math.MaxUint8))
rgba.B = uint8(math.Floor(p * math.MaxUint8))
case 1:
rgba.R = uint8(math.Floor(q * math.MaxUint8))
rgba.G = uint8(math.Floor(hsva.V * math.MaxUint8))
rgba.B = uint8(math.Floor(p * math.MaxUint8))
case 2:
rgba.R = uint8(math.Floor(p * math.MaxUint8))
rgba.G = uint8(math.Floor(hsva.V * math.MaxUint8))
rgba.B = uint8(math.Floor(t * math.MaxUint8))
case 3:
rgba.R = uint8(math.Floor(p * math.MaxUint8))
rgba.G = uint8(math.Floor(q * math.MaxUint8))
rgba.B = uint8(math.Floor(hsva.V * math.MaxUint8))
case 4:
rgba.R = uint8(math.Floor(t * math.MaxUint8))
rgba.G = uint8(math.Floor(p * math.MaxUint8))
rgba.B = uint8(math.Floor(hsva.V * math.MaxUint8))
default: // case 5
rgba.R = uint8(math.Floor(hsva.V * math.MaxUint8))
rgba.G = uint8(math.Floor(p * math.MaxUint8))
rgba.B = uint8(math.Floor(q * math.MaxUint8))
}
return rgba.RGBA()
}