1
2 package csscolorparser
3
4 import (
5 "fmt"
6 "math"
7 "strconv"
8 "strings"
9 )
10
11
12
13
14 type Color struct {
15 R, G, B, A float64
16 }
17
18
19 func (c Color) RGBA() (r, g, b, a uint32) {
20 r = uint32(c.R*c.A*65535 + 0.5)
21 g = uint32(c.G*c.A*65535 + 0.5)
22 b = uint32(c.B*c.A*65535 + 0.5)
23 a = uint32(c.A*65535 + 0.5)
24 return
25 }
26
27
28 func (c Color) RGBA255() (r, g, b, a uint8) {
29 r = uint8(c.R*255 + 0.5)
30 g = uint8(c.G*255 + 0.5)
31 b = uint8(c.B*255 + 0.5)
32 a = uint8(c.A*255 + 0.5)
33 return
34 }
35
36
37 func (c Color) HexString() string {
38 r, g, b, a := c.RGBA255()
39 if a < 255 {
40 return fmt.Sprintf("#%02x%02x%02x%02x", r, g, b, a)
41 }
42 return fmt.Sprintf("#%02x%02x%02x", r, g, b)
43 }
44
45
46 func (c Color) RGBString() string {
47 r, g, b, _ := c.RGBA255()
48 if c.A < 1 {
49 return fmt.Sprintf("rgba(%d,%d,%d,%v)", r, g, b, c.A)
50 }
51 return fmt.Sprintf("rgb(%d,%d,%d)", r, g, b)
52 }
53
54
55 func (c Color) Name() (string, bool) {
56 r, g, b, _ := c.RGBA255()
57 rgb := [3]uint8{r, g, b}
58 for k, v := range namedColors {
59 if v == rgb {
60 return k, true
61 }
62 }
63 return "", false
64 }
65
66
67 func (c *Color) UnmarshalText(text []byte) error {
68 col, err := Parse(string(text))
69 if err != nil {
70 return err
71 }
72 c.R = col.R
73 c.G = col.G
74 c.B = col.B
75 c.A = col.A
76 return nil
77 }
78
79
80 func (c Color) MarshalText() ([]byte, error) {
81 return []byte(c.HexString()), nil
82 }
83
84 var black = Color{0, 0, 0, 1}
85
86
87 func Parse(s string) (Color, error) {
88 input := s
89 s = strings.TrimSpace(strings.ToLower(s))
90
91 if s == "transparent" {
92 return Color{0, 0, 0, 0}, nil
93 }
94
95
96 c, ok := namedColors[s]
97 if ok {
98 return Color{float64(c[0]) / 255, float64(c[1]) / 255, float64(c[2]) / 255, 1}, nil
99 }
100
101
102 if strings.HasPrefix(s, "#") {
103 c, ok := parseHex(s[1:])
104 if ok {
105 return c, nil
106 }
107 return black, fmt.Errorf("Invalid hex color, %s", input)
108 }
109
110 op := strings.Index(s, "(")
111
112 if (op != -1) && strings.HasSuffix(s, ")") {
113 fname := strings.TrimSpace(s[:op])
114 alpha := 1.0
115 okA := true
116 s = s[op+1 : len(s)-1]
117 s = strings.ReplaceAll(s, ",", " ")
118 s = strings.ReplaceAll(s, "/", " ")
119 params := strings.Fields(s)
120
121 if fname == "rgb" || fname == "rgba" {
122 if len(params) != 3 && len(params) != 4 {
123 return black, fmt.Errorf("%s() format needs 3 or 4 parameters, %s", fname, input)
124 }
125 r, okR := parsePercentOr255(params[0])
126 g, okG := parsePercentOr255(params[1])
127 b, okB := parsePercentOr255(params[2])
128 if len(params) == 4 {
129 alpha, okA = parsePercentOrFloat(params[3])
130 }
131 if okR && okG && okB && okA {
132 return Color{
133 clamp0_1(r),
134 clamp0_1(g),
135 clamp0_1(b),
136 clamp0_1(alpha),
137 }, nil
138 }
139 return black, fmt.Errorf("Wrong %s() components, %s", fname, input)
140
141 } else if fname == "hsl" || fname == "hsla" {
142 if len(params) != 3 && len(params) != 4 {
143 return black, fmt.Errorf("%s() format needs 3 or 4 parameters, %s", fname, input)
144 }
145 h, okH := parseAngle(params[0])
146 s, okS := parsePercentOrFloat(params[1])
147 l, okL := parsePercentOrFloat(params[2])
148 if len(params) == 4 {
149 alpha, okA = parsePercentOrFloat(params[3])
150 }
151 if okH && okS && okL && okA {
152 r, g, b := hslToRgb(normalizeAngle(h), clamp0_1(s), clamp0_1(l))
153 return Color{r, g, b, clamp0_1(alpha)}, nil
154 }
155 return black, fmt.Errorf("Wrong %s() components, %s", fname, input)
156
157 } else if fname == "hwb" || fname == "hwba" {
158 if len(params) != 3 && len(params) != 4 {
159 return black, fmt.Errorf("hwb() format needs 3 or 4 parameters, %s", input)
160 }
161 H, okH := parseAngle(params[0])
162 W, okW := parsePercentOrFloat(params[1])
163 B, okB := parsePercentOrFloat(params[2])
164 if len(params) == 4 {
165 alpha, okA = parsePercentOrFloat(params[3])
166 }
167 if okH && okW && okB && okA {
168 r, g, b := hwbToRgb(normalizeAngle(H), clamp0_1(W), clamp0_1(B))
169 return Color{r, g, b, clamp0_1(alpha)}, nil
170 }
171 return black, fmt.Errorf("Wrong hwb() components, %s", input)
172
173 } else if fname == "hsv" || fname == "hsva" {
174 if len(params) != 3 && len(params) != 4 {
175 return black, fmt.Errorf("hsv() format needs 3 or 4 parameters, %s", input)
176 }
177 h, okH := parseAngle(params[0])
178 s, okS := parsePercentOrFloat(params[1])
179 v, okV := parsePercentOrFloat(params[2])
180 if len(params) == 4 {
181 alpha, okA = parsePercentOrFloat(params[3])
182 }
183 if okH && okS && okV && okA {
184 r, g, b := hsvToRgb(normalizeAngle(h), clamp0_1(s), clamp0_1(v))
185 return Color{r, g, b, clamp0_1(alpha)}, nil
186 }
187 return black, fmt.Errorf("Wrong hsv() components, %s", input)
188 }
189 }
190
191
192 c2, ok2 := parseHex(s)
193 if ok2 {
194 return c2, nil
195 }
196
197 return black, fmt.Errorf("Invalid color format, %s", input)
198 }
199
200
201
202 func parseHex(s string) (c Color, ok bool) {
203 c.A = 1
204 ok = true
205
206 hexToByte := func(b byte) byte {
207 switch {
208 case b >= '0' && b <= '9':
209 return b - '0'
210 case b >= 'a' && b <= 'f':
211 return b - 'a' + 10
212 }
213 ok = false
214 return 0
215 }
216
217 n := len(s)
218 if n == 6 || n == 8 {
219 c.R = float64(hexToByte(s[0])<<4+hexToByte(s[1])) / 255
220 c.G = float64(hexToByte(s[2])<<4+hexToByte(s[3])) / 255
221 c.B = float64(hexToByte(s[4])<<4+hexToByte(s[5])) / 255
222 if n == 8 {
223 c.A = float64(hexToByte(s[6])<<4+hexToByte(s[7])) / 255
224 }
225 } else if n == 3 || n == 4 {
226 c.R = float64(hexToByte(s[0])*17) / 255
227 c.G = float64(hexToByte(s[1])*17) / 255
228 c.B = float64(hexToByte(s[2])*17) / 255
229 if n == 4 {
230 c.A = float64(hexToByte(s[3])*17) / 255
231 }
232 } else {
233 ok = false
234 }
235 return
236 }
237
238 func modulo(x, y float64) float64 {
239 return math.Mod(math.Mod(x, y)+y, y)
240 }
241
242 func hueToRgb(n1, n2, h float64) float64 {
243 h = modulo(h, 6)
244 if h < 1 {
245 return n1 + ((n2 - n1) * h)
246 }
247 if h < 3 {
248 return n2
249 }
250 if h < 4 {
251 return n1 + ((n2 - n1) * (4 - h))
252 }
253 return n1
254 }
255
256
257
258
259 func hslToRgb(h, s, l float64) (r, g, b float64) {
260 if s == 0 {
261 return l, l, l
262 }
263 var n2 float64
264 if l < 0.5 {
265 n2 = l * (1 + s)
266 } else {
267 n2 = l + s - (l * s)
268 }
269 n1 := 2*l - n2
270 h /= 60
271 r = clamp0_1(hueToRgb(n1, n2, h+2))
272 g = clamp0_1(hueToRgb(n1, n2, h))
273 b = clamp0_1(hueToRgb(n1, n2, h-2))
274 return
275 }
276
277 func hwbToRgb(hue, white, black float64) (r, g, b float64) {
278 if white+black >= 1 {
279 gray := white / (white + black)
280 return gray, gray, gray
281 }
282 r, g, b = hslToRgb(hue, 1, 0.5)
283 r = r*(1-white-black) + white
284 g = g*(1-white-black) + white
285 b = b*(1-white-black) + white
286 return
287 }
288
289 func hsvToHsl(H, S, V float64) (h, s, l float64) {
290 h = H
291 s = S
292 l = (2 - S) * V / 2
293 if l != 0 {
294 if l == 1 {
295 s = 0
296 } else if l < 0.5 {
297 s = S * V / (l * 2)
298 } else {
299 s = S * V / (2 - l*2)
300 }
301 }
302 return
303 }
304
305 func hsvToRgb(H, S, V float64) (r, g, b float64) {
306 h, s, l := hsvToHsl(H, S, V)
307 return hslToRgb(h, s, l)
308 }
309
310 func clamp0_1(t float64) float64 {
311 if t < 0 {
312 return 0
313 }
314 if t > 1 {
315 return 1
316 }
317 return t
318 }
319
320 func parseFloat(s string) (float64, bool) {
321 f, err := strconv.ParseFloat(strings.TrimSpace(s), 64)
322 return f, err == nil
323 }
324
325 func parsePercentOrFloat(s string) (float64, bool) {
326 if strings.HasSuffix(s, "%") {
327 f, ok := parseFloat(s[:len(s)-1])
328 if ok {
329 return f / 100, true
330 }
331 return 0, false
332 }
333 return parseFloat(s)
334 }
335
336 func parsePercentOr255(s string) (float64, bool) {
337 if strings.HasSuffix(s, "%") {
338 f, ok := parseFloat(s[:len(s)-1])
339 if ok {
340 return f / 100, true
341 }
342 return 0, false
343 }
344 f, ok := parseFloat(s)
345 if ok {
346 return f / 255, true
347 }
348 return 0, false
349 }
350
351
352 func parseAngle(s string) (float64, bool) {
353 if strings.HasSuffix(s, "deg") {
354 return parseFloat(s[:len(s)-3])
355 }
356 if strings.HasSuffix(s, "grad") {
357 f, ok := parseFloat(s[:len(s)-4])
358 if ok {
359 return f / 400 * 360, true
360 }
361 return 0, false
362 }
363 if strings.HasSuffix(s, "rad") {
364 f, ok := parseFloat(s[:len(s)-3])
365 if ok {
366 return f / math.Pi * 180, true
367 }
368 return 0, false
369 }
370 if strings.HasSuffix(s, "turn") {
371 f, ok := parseFloat(s[:len(s)-4])
372 if ok {
373 return f * 360, true
374 }
375 return 0, false
376 }
377 return parseFloat(s)
378 }
379
380 func normalizeAngle(t float64) float64 {
381 t = math.Mod(t, 360)
382 if t < 0 {
383 t += 360
384 }
385 return t
386 }
387
View as plain text