| 535 | } |
| 536 | |
| 537 | function generateGrayScale(bg: RGBA, isDark: boolean): Record<number, RGBA> { |
| 538 | const grays: Record<number, RGBA> = {} |
| 539 | |
| 540 | // RGBA stores floats in range 0-1, convert to 0-255 |
| 541 | const bgR = bg.r * 255 |
| 542 | const bgG = bg.g * 255 |
| 543 | const bgB = bg.b * 255 |
| 544 | |
| 545 | const luminance = 0.299 * bgR + 0.587 * bgG + 0.114 * bgB |
| 546 | |
| 547 | for (let i = 1; i <= 12; i++) { |
| 548 | const factor = i / 12.0 |
| 549 | |
| 550 | let grayValue: number |
| 551 | let newR: number |
| 552 | let newG: number |
| 553 | let newB: number |
| 554 | |
| 555 | if (isDark) { |
| 556 | if (luminance < 10) { |
| 557 | grayValue = Math.floor(factor * 0.4 * 255) |
| 558 | newR = grayValue |
| 559 | newG = grayValue |
| 560 | newB = grayValue |
| 561 | } else { |
| 562 | const newLum = luminance + (255 - luminance) * factor * 0.4 |
| 563 | |
| 564 | const ratio = newLum / luminance |
| 565 | newR = Math.min(bgR * ratio, 255) |
| 566 | newG = Math.min(bgG * ratio, 255) |
| 567 | newB = Math.min(bgB * ratio, 255) |
| 568 | } |
| 569 | } else { |
| 570 | if (luminance > 245) { |
| 571 | grayValue = Math.floor(255 - factor * 0.4 * 255) |
| 572 | newR = grayValue |
| 573 | newG = grayValue |
| 574 | newB = grayValue |
| 575 | } else { |
| 576 | const newLum = luminance * (1 - factor * 0.4) |
| 577 | |
| 578 | const ratio = newLum / luminance |
| 579 | newR = Math.max(bgR * ratio, 0) |
| 580 | newG = Math.max(bgG * ratio, 0) |
| 581 | newB = Math.max(bgB * ratio, 0) |
| 582 | } |
| 583 | } |
| 584 | |
| 585 | grays[i] = RGBA.fromInts(Math.floor(newR), Math.floor(newG), Math.floor(newB)) |
| 586 | } |
| 587 | |
| 588 | return grays |
| 589 | } |
| 590 | |
| 591 | function generateMutedTextColor(bg: RGBA, isDark: boolean): RGBA { |
| 592 | // RGBA stores floats in range 0-1, convert to 0-255 |