1package 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/onsi/ginkgo/reporters/stenographer/support/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} 784