|
| 1 | +function toLinearRgb(channel: number): number { |
| 2 | + const srgb = channel / 255 |
| 3 | + |
| 4 | + return srgb <= 0.04045 ? srgb / 12.92 : ((srgb + 0.055) / 1.055) ** 2.4 |
| 5 | +} |
| 6 | + |
1 | 7 | export function getContrastTextColor(hex: string): string { |
2 | | - const normalizedHex = hex.replace('#', '') |
3 | | - const red = Number.parseInt(normalizedHex.substring(0, 2), 16) |
4 | | - const green = Number.parseInt(normalizedHex.substring(2, 4), 16) |
5 | | - const blue = Number.parseInt(normalizedHex.substring(4, 6), 16) |
6 | | - const luminance = (0.299 * red + 0.587 * green + 0.114 * blue) / 255 |
| 8 | + const normalizedHex = hex.trim().replace(/^#/, '') |
| 9 | + |
| 10 | + if (!/^[0-9a-fA-F]{6}$/.test(normalizedHex)) { |
| 11 | + return '#000000' |
| 12 | + } |
| 13 | + |
| 14 | + const red = Number.parseInt(normalizedHex.slice(0, 2), 16) |
| 15 | + const green = Number.parseInt(normalizedHex.slice(2, 4), 16) |
| 16 | + const blue = Number.parseInt(normalizedHex.slice(4, 6), 16) |
| 17 | + |
| 18 | + const luminance = |
| 19 | + 0.2126 * toLinearRgb(red) + |
| 20 | + 0.7152 * toLinearRgb(green) + |
| 21 | + 0.0722 * toLinearRgb(blue) |
| 22 | + |
| 23 | + const contrastWithBlack = (luminance + 0.05) / 0.05 |
| 24 | + const contrastWithWhite = 1.05 / (luminance + 0.05) |
7 | 25 |
|
8 | | - return luminance > 0.5 ? '#000000' : '#ffffff' |
| 26 | + return contrastWithBlack >= contrastWithWhite ? '#000000' : '#ffffff' |
9 | 27 | } |
0 commit comments