blob: c8f4e096344d17753d881f6758e2a970519dc0a6 [file] [log] [blame]
David K. Bainbridge215e0242017-09-05 23:18:24 -07001package colorable
2
3import (
4 "bytes"
5 "fmt"
6 "io"
7 "math"
8 "os"
9 "strconv"
10 "strings"
11 "syscall"
12 "unsafe"
13
14 "github.com/mattn/go-isatty"
15)
16
17const (
18 foregroundBlue = 0x1
19 foregroundGreen = 0x2
20 foregroundRed = 0x4
21 foregroundIntensity = 0x8
22 foregroundMask = (foregroundRed | foregroundBlue | foregroundGreen | foregroundIntensity)
23 backgroundBlue = 0x10
24 backgroundGreen = 0x20
25 backgroundRed = 0x40
26 backgroundIntensity = 0x80
27 backgroundMask = (backgroundRed | backgroundBlue | backgroundGreen | backgroundIntensity)
28)
29
30type wchar uint16
31type short int16
32type dword uint32
33type word uint16
34
35type coord struct {
36 x short
37 y short
38}
39
40type smallRect struct {
41 left short
42 top short
43 right short
44 bottom short
45}
46
47type consoleScreenBufferInfo struct {
48 size coord
49 cursorPosition coord
50 attributes word
51 window smallRect
52 maximumWindowSize coord
53}
54
55var (
56 kernel32 = syscall.NewLazyDLL("kernel32.dll")
57 procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
58 procSetConsoleTextAttribute = kernel32.NewProc("SetConsoleTextAttribute")
59 procSetConsoleCursorPosition = kernel32.NewProc("SetConsoleCursorPosition")
60 procFillConsoleOutputCharacter = kernel32.NewProc("FillConsoleOutputCharacterW")
61 procFillConsoleOutputAttribute = kernel32.NewProc("FillConsoleOutputAttribute")
62)
63
64type Writer struct {
65 out io.Writer
66 handle syscall.Handle
67 lastbuf bytes.Buffer
68 oldattr word
69}
70
71func NewColorable(file *os.File) io.Writer {
72 if file == nil {
73 panic("nil passed instead of *os.File to NewColorable()")
74 }
75
76 if isatty.IsTerminal(file.Fd()) {
77 var csbi consoleScreenBufferInfo
78 handle := syscall.Handle(file.Fd())
79 procGetConsoleScreenBufferInfo.Call(uintptr(handle), uintptr(unsafe.Pointer(&csbi)))
80 return &Writer{out: file, handle: handle, oldattr: csbi.attributes}
81 } else {
82 return file
83 }
84}
85
86func NewColorableStdout() io.Writer {
87 return NewColorable(os.Stdout)
88}
89
90func NewColorableStderr() io.Writer {
91 return NewColorable(os.Stderr)
92}
93
94var color256 = map[int]int{
95 0: 0x000000,
96 1: 0x800000,
97 2: 0x008000,
98 3: 0x808000,
99 4: 0x000080,
100 5: 0x800080,
101 6: 0x008080,
102 7: 0xc0c0c0,
103 8: 0x808080,
104 9: 0xff0000,
105 10: 0x00ff00,
106 11: 0xffff00,
107 12: 0x0000ff,
108 13: 0xff00ff,
109 14: 0x00ffff,
110 15: 0xffffff,
111 16: 0x000000,
112 17: 0x00005f,
113 18: 0x000087,
114 19: 0x0000af,
115 20: 0x0000d7,
116 21: 0x0000ff,
117 22: 0x005f00,
118 23: 0x005f5f,
119 24: 0x005f87,
120 25: 0x005faf,
121 26: 0x005fd7,
122 27: 0x005fff,
123 28: 0x008700,
124 29: 0x00875f,
125 30: 0x008787,
126 31: 0x0087af,
127 32: 0x0087d7,
128 33: 0x0087ff,
129 34: 0x00af00,
130 35: 0x00af5f,
131 36: 0x00af87,
132 37: 0x00afaf,
133 38: 0x00afd7,
134 39: 0x00afff,
135 40: 0x00d700,
136 41: 0x00d75f,
137 42: 0x00d787,
138 43: 0x00d7af,
139 44: 0x00d7d7,
140 45: 0x00d7ff,
141 46: 0x00ff00,
142 47: 0x00ff5f,
143 48: 0x00ff87,
144 49: 0x00ffaf,
145 50: 0x00ffd7,
146 51: 0x00ffff,
147 52: 0x5f0000,
148 53: 0x5f005f,
149 54: 0x5f0087,
150 55: 0x5f00af,
151 56: 0x5f00d7,
152 57: 0x5f00ff,
153 58: 0x5f5f00,
154 59: 0x5f5f5f,
155 60: 0x5f5f87,
156 61: 0x5f5faf,
157 62: 0x5f5fd7,
158 63: 0x5f5fff,
159 64: 0x5f8700,
160 65: 0x5f875f,
161 66: 0x5f8787,
162 67: 0x5f87af,
163 68: 0x5f87d7,
164 69: 0x5f87ff,
165 70: 0x5faf00,
166 71: 0x5faf5f,
167 72: 0x5faf87,
168 73: 0x5fafaf,
169 74: 0x5fafd7,
170 75: 0x5fafff,
171 76: 0x5fd700,
172 77: 0x5fd75f,
173 78: 0x5fd787,
174 79: 0x5fd7af,
175 80: 0x5fd7d7,
176 81: 0x5fd7ff,
177 82: 0x5fff00,
178 83: 0x5fff5f,
179 84: 0x5fff87,
180 85: 0x5fffaf,
181 86: 0x5fffd7,
182 87: 0x5fffff,
183 88: 0x870000,
184 89: 0x87005f,
185 90: 0x870087,
186 91: 0x8700af,
187 92: 0x8700d7,
188 93: 0x8700ff,
189 94: 0x875f00,
190 95: 0x875f5f,
191 96: 0x875f87,
192 97: 0x875faf,
193 98: 0x875fd7,
194 99: 0x875fff,
195 100: 0x878700,
196 101: 0x87875f,
197 102: 0x878787,
198 103: 0x8787af,
199 104: 0x8787d7,
200 105: 0x8787ff,
201 106: 0x87af00,
202 107: 0x87af5f,
203 108: 0x87af87,
204 109: 0x87afaf,
205 110: 0x87afd7,
206 111: 0x87afff,
207 112: 0x87d700,
208 113: 0x87d75f,
209 114: 0x87d787,
210 115: 0x87d7af,
211 116: 0x87d7d7,
212 117: 0x87d7ff,
213 118: 0x87ff00,
214 119: 0x87ff5f,
215 120: 0x87ff87,
216 121: 0x87ffaf,
217 122: 0x87ffd7,
218 123: 0x87ffff,
219 124: 0xaf0000,
220 125: 0xaf005f,
221 126: 0xaf0087,
222 127: 0xaf00af,
223 128: 0xaf00d7,
224 129: 0xaf00ff,
225 130: 0xaf5f00,
226 131: 0xaf5f5f,
227 132: 0xaf5f87,
228 133: 0xaf5faf,
229 134: 0xaf5fd7,
230 135: 0xaf5fff,
231 136: 0xaf8700,
232 137: 0xaf875f,
233 138: 0xaf8787,
234 139: 0xaf87af,
235 140: 0xaf87d7,
236 141: 0xaf87ff,
237 142: 0xafaf00,
238 143: 0xafaf5f,
239 144: 0xafaf87,
240 145: 0xafafaf,
241 146: 0xafafd7,
242 147: 0xafafff,
243 148: 0xafd700,
244 149: 0xafd75f,
245 150: 0xafd787,
246 151: 0xafd7af,
247 152: 0xafd7d7,
248 153: 0xafd7ff,
249 154: 0xafff00,
250 155: 0xafff5f,
251 156: 0xafff87,
252 157: 0xafffaf,
253 158: 0xafffd7,
254 159: 0xafffff,
255 160: 0xd70000,
256 161: 0xd7005f,
257 162: 0xd70087,
258 163: 0xd700af,
259 164: 0xd700d7,
260 165: 0xd700ff,
261 166: 0xd75f00,
262 167: 0xd75f5f,
263 168: 0xd75f87,
264 169: 0xd75faf,
265 170: 0xd75fd7,
266 171: 0xd75fff,
267 172: 0xd78700,
268 173: 0xd7875f,
269 174: 0xd78787,
270 175: 0xd787af,
271 176: 0xd787d7,
272 177: 0xd787ff,
273 178: 0xd7af00,
274 179: 0xd7af5f,
275 180: 0xd7af87,
276 181: 0xd7afaf,
277 182: 0xd7afd7,
278 183: 0xd7afff,
279 184: 0xd7d700,
280 185: 0xd7d75f,
281 186: 0xd7d787,
282 187: 0xd7d7af,
283 188: 0xd7d7d7,
284 189: 0xd7d7ff,
285 190: 0xd7ff00,
286 191: 0xd7ff5f,
287 192: 0xd7ff87,
288 193: 0xd7ffaf,
289 194: 0xd7ffd7,
290 195: 0xd7ffff,
291 196: 0xff0000,
292 197: 0xff005f,
293 198: 0xff0087,
294 199: 0xff00af,
295 200: 0xff00d7,
296 201: 0xff00ff,
297 202: 0xff5f00,
298 203: 0xff5f5f,
299 204: 0xff5f87,
300 205: 0xff5faf,
301 206: 0xff5fd7,
302 207: 0xff5fff,
303 208: 0xff8700,
304 209: 0xff875f,
305 210: 0xff8787,
306 211: 0xff87af,
307 212: 0xff87d7,
308 213: 0xff87ff,
309 214: 0xffaf00,
310 215: 0xffaf5f,
311 216: 0xffaf87,
312 217: 0xffafaf,
313 218: 0xffafd7,
314 219: 0xffafff,
315 220: 0xffd700,
316 221: 0xffd75f,
317 222: 0xffd787,
318 223: 0xffd7af,
319 224: 0xffd7d7,
320 225: 0xffd7ff,
321 226: 0xffff00,
322 227: 0xffff5f,
323 228: 0xffff87,
324 229: 0xffffaf,
325 230: 0xffffd7,
326 231: 0xffffff,
327 232: 0x080808,
328 233: 0x121212,
329 234: 0x1c1c1c,
330 235: 0x262626,
331 236: 0x303030,
332 237: 0x3a3a3a,
333 238: 0x444444,
334 239: 0x4e4e4e,
335 240: 0x585858,
336 241: 0x626262,
337 242: 0x6c6c6c,
338 243: 0x767676,
339 244: 0x808080,
340 245: 0x8a8a8a,
341 246: 0x949494,
342 247: 0x9e9e9e,
343 248: 0xa8a8a8,
344 249: 0xb2b2b2,
345 250: 0xbcbcbc,
346 251: 0xc6c6c6,
347 252: 0xd0d0d0,
348 253: 0xdadada,
349 254: 0xe4e4e4,
350 255: 0xeeeeee,
351}
352
353func (w *Writer) Write(data []byte) (n int, err error) {
354 var csbi consoleScreenBufferInfo
355 procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
356
357 er := bytes.NewBuffer(data)
358loop:
359 for {
360 r1, _, err := procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
361 if r1 == 0 {
362 break loop
363 }
364
365 c1, _, err := er.ReadRune()
366 if err != nil {
367 break loop
368 }
369 if c1 != 0x1b {
370 fmt.Fprint(w.out, string(c1))
371 continue
372 }
373 c2, _, err := er.ReadRune()
374 if err != nil {
375 w.lastbuf.WriteRune(c1)
376 break loop
377 }
378 if c2 != 0x5b {
379 w.lastbuf.WriteRune(c1)
380 w.lastbuf.WriteRune(c2)
381 continue
382 }
383
384 var buf bytes.Buffer
385 var m rune
386 for {
387 c, _, err := er.ReadRune()
388 if err != nil {
389 w.lastbuf.WriteRune(c1)
390 w.lastbuf.WriteRune(c2)
391 w.lastbuf.Write(buf.Bytes())
392 break loop
393 }
394 if ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || c == '@' {
395 m = c
396 break
397 }
398 buf.Write([]byte(string(c)))
399 }
400
401 var csbi consoleScreenBufferInfo
402 switch m {
403 case 'A':
404 n, err = strconv.Atoi(buf.String())
405 if err != nil {
406 continue
407 }
408 procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
409 csbi.cursorPosition.y -= short(n)
410 procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
411 case 'B':
412 n, err = strconv.Atoi(buf.String())
413 if err != nil {
414 continue
415 }
416 procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
417 csbi.cursorPosition.y += short(n)
418 procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
419 case 'C':
420 n, err = strconv.Atoi(buf.String())
421 if err != nil {
422 continue
423 }
424 procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
425 csbi.cursorPosition.x -= short(n)
426 procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
427 case 'D':
428 n, err = strconv.Atoi(buf.String())
429 if err != nil {
430 continue
431 }
432 if n, err = strconv.Atoi(buf.String()); err == nil {
433 var csbi consoleScreenBufferInfo
434 procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
435 csbi.cursorPosition.x += short(n)
436 procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
437 }
438 case 'E':
439 n, err = strconv.Atoi(buf.String())
440 if err != nil {
441 continue
442 }
443 procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
444 csbi.cursorPosition.x = 0
445 csbi.cursorPosition.y += short(n)
446 procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
447 case 'F':
448 n, err = strconv.Atoi(buf.String())
449 if err != nil {
450 continue
451 }
452 procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
453 csbi.cursorPosition.x = 0
454 csbi.cursorPosition.y -= short(n)
455 procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
456 case 'G':
457 n, err = strconv.Atoi(buf.String())
458 if err != nil {
459 continue
460 }
461 procGetConsoleScreenBufferInfo.Call(uintptr(w.handle), uintptr(unsafe.Pointer(&csbi)))
462 csbi.cursorPosition.x = short(n)
463 procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
464 case 'H':
465 token := strings.Split(buf.String(), ";")
466 if len(token) != 2 {
467 continue
468 }
469 n1, err := strconv.Atoi(token[0])
470 if err != nil {
471 continue
472 }
473 n2, err := strconv.Atoi(token[1])
474 if err != nil {
475 continue
476 }
477 csbi.cursorPosition.x = short(n2)
478 csbi.cursorPosition.x = short(n1)
479 procSetConsoleCursorPosition.Call(uintptr(w.handle), *(*uintptr)(unsafe.Pointer(&csbi.cursorPosition)))
480 case 'J':
481 n, err := strconv.Atoi(buf.String())
482 if err != nil {
483 continue
484 }
485 var cursor coord
486 switch n {
487 case 0:
488 cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y}
489 case 1:
490 cursor = coord{x: csbi.window.left, y: csbi.window.top}
491 case 2:
492 cursor = coord{x: csbi.window.left, y: csbi.window.top}
493 }
494 var count, written dword
495 count = dword(csbi.size.x - csbi.cursorPosition.x + (csbi.size.y-csbi.cursorPosition.y)*csbi.size.x)
496 procFillConsoleOutputCharacter.Call(uintptr(w.handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
497 procFillConsoleOutputAttribute.Call(uintptr(w.handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
498 case 'K':
499 n, err := strconv.Atoi(buf.String())
500 if err != nil {
501 continue
502 }
503 var cursor coord
504 switch n {
505 case 0:
506 cursor = coord{x: csbi.cursorPosition.x, y: csbi.cursorPosition.y}
507 case 1:
508 cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y}
509 case 2:
510 cursor = coord{x: csbi.window.left, y: csbi.window.top + csbi.cursorPosition.y}
511 }
512 var count, written dword
513 count = dword(csbi.size.x - csbi.cursorPosition.x)
514 procFillConsoleOutputCharacter.Call(uintptr(w.handle), uintptr(' '), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
515 procFillConsoleOutputAttribute.Call(uintptr(w.handle), uintptr(csbi.attributes), uintptr(count), *(*uintptr)(unsafe.Pointer(&cursor)), uintptr(unsafe.Pointer(&written)))
516 case 'm':
517 attr := csbi.attributes
518 cs := buf.String()
519 if cs == "" {
520 procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(w.oldattr))
521 continue
522 }
523 token := strings.Split(cs, ";")
524 for i := 0; i < len(token); i += 1 {
525 ns := token[i]
526 if n, err = strconv.Atoi(ns); err == nil {
527 switch {
528 case n == 0 || n == 100:
529 attr = w.oldattr
530 case 1 <= n && n <= 5:
531 attr |= foregroundIntensity
532 case n == 7:
533 attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4)
534 case 22 == n || n == 25 || n == 25:
535 attr |= foregroundIntensity
536 case n == 27:
537 attr = ((attr & foregroundMask) << 4) | ((attr & backgroundMask) >> 4)
538 case 30 <= n && n <= 37:
539 attr = (attr & backgroundMask)
540 if (n-30)&1 != 0 {
541 attr |= foregroundRed
542 }
543 if (n-30)&2 != 0 {
544 attr |= foregroundGreen
545 }
546 if (n-30)&4 != 0 {
547 attr |= foregroundBlue
548 }
549 case n == 38: // set foreground color.
550 if i < len(token)-2 && (token[i+1] == "5" || token[i+1] == "05") {
551 if n256, err := strconv.Atoi(token[i+2]); err == nil {
552 if n256foreAttr == nil {
553 n256setup()
554 }
555 attr &= backgroundMask
556 attr |= n256foreAttr[n256]
557 i += 2
558 }
559 } else {
560 attr = attr & (w.oldattr & backgroundMask)
561 }
562 case n == 39: // reset foreground color.
563 attr &= backgroundMask
564 attr |= w.oldattr & foregroundMask
565 case 40 <= n && n <= 47:
566 attr = (attr & foregroundMask)
567 if (n-40)&1 != 0 {
568 attr |= backgroundRed
569 }
570 if (n-40)&2 != 0 {
571 attr |= backgroundGreen
572 }
573 if (n-40)&4 != 0 {
574 attr |= backgroundBlue
575 }
576 case n == 48: // set background color.
577 if i < len(token)-2 && token[i+1] == "5" {
578 if n256, err := strconv.Atoi(token[i+2]); err == nil {
579 if n256backAttr == nil {
580 n256setup()
581 }
582 attr &= foregroundMask
583 attr |= n256backAttr[n256]
584 i += 2
585 }
586 } else {
587 attr = attr & (w.oldattr & foregroundMask)
588 }
589 case n == 49: // reset foreground color.
590 attr &= foregroundMask
591 attr |= w.oldattr & backgroundMask
592 case 90 <= n && n <= 97:
593 attr = (attr & backgroundMask)
594 attr |= foregroundIntensity
595 if (n-90)&1 != 0 {
596 attr |= foregroundRed
597 }
598 if (n-90)&2 != 0 {
599 attr |= foregroundGreen
600 }
601 if (n-90)&4 != 0 {
602 attr |= foregroundBlue
603 }
604 case 100 <= n && n <= 107:
605 attr = (attr & foregroundMask)
606 attr |= backgroundIntensity
607 if (n-100)&1 != 0 {
608 attr |= backgroundRed
609 }
610 if (n-100)&2 != 0 {
611 attr |= backgroundGreen
612 }
613 if (n-100)&4 != 0 {
614 attr |= backgroundBlue
615 }
616 }
617 procSetConsoleTextAttribute.Call(uintptr(w.handle), uintptr(attr))
618 }
619 }
620 }
621 }
622 return len(data) - w.lastbuf.Len(), nil
623}
624
625type consoleColor struct {
626 rgb int
627 red bool
628 green bool
629 blue bool
630 intensity bool
631}
632
633func (c consoleColor) foregroundAttr() (attr word) {
634 if c.red {
635 attr |= foregroundRed
636 }
637 if c.green {
638 attr |= foregroundGreen
639 }
640 if c.blue {
641 attr |= foregroundBlue
642 }
643 if c.intensity {
644 attr |= foregroundIntensity
645 }
646 return
647}
648
649func (c consoleColor) backgroundAttr() (attr word) {
650 if c.red {
651 attr |= backgroundRed
652 }
653 if c.green {
654 attr |= backgroundGreen
655 }
656 if c.blue {
657 attr |= backgroundBlue
658 }
659 if c.intensity {
660 attr |= backgroundIntensity
661 }
662 return
663}
664
665var color16 = []consoleColor{
666 consoleColor{0x000000, false, false, false, false},
667 consoleColor{0x000080, false, false, true, false},
668 consoleColor{0x008000, false, true, false, false},
669 consoleColor{0x008080, false, true, true, false},
670 consoleColor{0x800000, true, false, false, false},
671 consoleColor{0x800080, true, false, true, false},
672 consoleColor{0x808000, true, true, false, false},
673 consoleColor{0xc0c0c0, true, true, true, false},
674 consoleColor{0x808080, false, false, false, true},
675 consoleColor{0x0000ff, false, false, true, true},
676 consoleColor{0x00ff00, false, true, false, true},
677 consoleColor{0x00ffff, false, true, true, true},
678 consoleColor{0xff0000, true, false, false, true},
679 consoleColor{0xff00ff, true, false, true, true},
680 consoleColor{0xffff00, true, true, false, true},
681 consoleColor{0xffffff, true, true, true, true},
682}
683
684type hsv struct {
685 h, s, v float32
686}
687
688func (a hsv) dist(b hsv) float32 {
689 dh := a.h - b.h
690 switch {
691 case dh > 0.5:
692 dh = 1 - dh
693 case dh < -0.5:
694 dh = -1 - dh
695 }
696 ds := a.s - b.s
697 dv := a.v - b.v
698 return float32(math.Sqrt(float64(dh*dh + ds*ds + dv*dv)))
699}
700
701func toHSV(rgb int) hsv {
702 r, g, b := float32((rgb&0xFF0000)>>16)/256.0,
703 float32((rgb&0x00FF00)>>8)/256.0,
704 float32(rgb&0x0000FF)/256.0
705 min, max := minmax3f(r, g, b)
706 h := max - min
707 if h > 0 {
708 if max == r {
709 h = (g - b) / h
710 if h < 0 {
711 h += 6
712 }
713 } else if max == g {
714 h = 2 + (b-r)/h
715 } else {
716 h = 4 + (r-g)/h
717 }
718 }
719 h /= 6.0
720 s := max - min
721 if max != 0 {
722 s /= max
723 }
724 v := max
725 return hsv{h: h, s: s, v: v}
726}
727
728type hsvTable []hsv
729
730func toHSVTable(rgbTable []consoleColor) hsvTable {
731 t := make(hsvTable, len(rgbTable))
732 for i, c := range rgbTable {
733 t[i] = toHSV(c.rgb)
734 }
735 return t
736}
737
738func (t hsvTable) find(rgb int) consoleColor {
739 hsv := toHSV(rgb)
740 n := 7
741 l := float32(5.0)
742 for i, p := range t {
743 d := hsv.dist(p)
744 if d < l {
745 l, n = d, i
746 }
747 }
748 return color16[n]
749}
750
751func minmax3f(a, b, c float32) (min, max float32) {
752 if a < b {
753 if b < c {
754 return a, c
755 } else if a < c {
756 return a, b
757 } else {
758 return c, b
759 }
760 } else {
761 if a < c {
762 return b, c
763 } else if b < c {
764 return b, a
765 } else {
766 return c, a
767 }
768 }
769}
770
771var n256foreAttr []word
772var n256backAttr []word
773
774func n256setup() {
775 n256foreAttr = make([]word, 256)
776 n256backAttr = make([]word, 256)
777 t := toHSVTable(color16)
778 for i, rgb := range color256 {
779 c := t.find(rgb)
780 n256foreAttr[i] = c.foregroundAttr()
781 n256backAttr[i] = c.backgroundAttr()
782 }
783}