Passed
Push — master ( 30a113...a21ddc )
by Eduardo
02:03
created

index.ts ➔ contrastFromHSP   A

Complexity

Conditions 2

Size

Total Lines 22
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 14
dl 0
loc 22
c 0
b 0
f 0
rs 9.7
cc 2
1
const hashRegEx = /#/
2
const hexRegEx = /0x/i
3
let isRGB: boolean
4
let isArray: boolean
5
let isHexString: boolean
6
let isHexNumber: boolean
7
8
type ColorIntensity = number | string
9
10
/**
11
 * Analyses the color (normally used in the background) and retrieves what color (black or white) has a better contrast.
12
 * @param hex The hex color number that must be a valid hexadecimal color number, with 6 characters, to work correctly
13
 * @example fontColorContrast(0XF3DC56) === fontColorContrast(15981654)
14
 */
15
function fontColorContrast (hex: number): '#ffffff' | '#000000'
16
17
/**
18
 * Analyses the color (normally used in the background) and retrieves what color (black or white) has a better contrast.
19
 * @param hex The hex color string that must be a valid hexadecima color number to work correctly. Works with or without '#', with 3 or 6 color chars
20
 * @example fontColorContrast('00FFDD') === fontColorContrast('0FD') === fontColorContrast('#00FFDD') === fontColorContrast('#0FD')
21
 */
22
 function fontColorContrast (hex: string): '#ffffff' | '#000000'
23
24
/**
25
 * Analyses the color (normally used in the background) and retrieves what color (black or white) has a better contrast.
26
 * @param red The red portion of the color. Must be a number between 0 and 255
27
 * @param green The green portion of the color. Must be a number between 0 and 255
28
 * @param blue The blue portion of the color. Must be a number between 0 and 255
29
 * @example fontColorContrast('00', 'F3', D8) === fontColorContrast(0, 243, 216) === fontColorContrast(0x0, 0xF3, 0xd8)
30
 */
31
function fontColorContrast (red: number, green: number, blue: number): '#ffffff' | '#000000'
32
33
/**
34
 * Analyses the color (normally used in the background) and retrieves what color (black or white) has a better contrast.
35
 * @param redGreenBlue Array with red, green and blue. Each value must be a number between 0 and 255
36
 * @example fontColorContrast(['00', 'F3', 'D8']) === fontColorContrast([0, 243, 216]) === fontColorContrast([0x0, 0xF3, 0xd8])
37
 */
38
function fontColorContrast (redGreenBlue: number[]): '#ffffff' | '#000000'
39
40
function fontColorContrast (hexColorOrRedOrArray: string | number | number[], green?: number, blue?: number) {
41
  let red = 0
42
  isRGB = !!(green !== undefined && blue !== undefined)
43
  isArray = Array.isArray(hexColorOrRedOrArray)
44
  isHexString = typeof hexColorOrRedOrArray === 'string' && !isRGB
45
  isHexNumber = typeof hexColorOrRedOrArray === 'number' && !isRGB
46
47
  if (isHexString || isHexNumber) {
48
    [red, green, blue] = hexColorToRGB(hexColorOrRedOrArray as ColorIntensity)
49
  } else if (isRGB || isArray) {
50
    [red, green, blue] = arrayOrRgbToRGB(hexColorOrRedOrArray as number | number[], green as number, blue as number)
51
  } else {
52
    // Not a color, respond with white color
53
    return '#ffffff'
54
  }
55
56
  return contrastFromHSP(red, green, blue)
57
}
58
59
export default fontColorContrast
60
61
/**
62
 * Converts a hexadecimal string to it's correspondent integer
63
 * @param hexString The hexadecimal string
64
 * @returns The integer value
65
 */
66
function hexToDec (hexString: string): number {
67
  const decString = (hexString).replace(/[^a-f0-9]/gi, '')
68
  return parseInt(decString, 16)
69
}
70
71
/**
72
 * Converts a ColorIntensity string or number, with all possibilities (e.g. '#009', '009', '#000099', '000099', 153, 0x00099) to the respective RGB values
73
 * @param hexColor The color string or number
74
 * @returns The array with the RGB values
75
 * @example All these examples produces the same value
76
 * hexColorToRGB('#0C9')
77
 * hexColorToRGB('0C9')
78
 * hexColorToRGB('#00CC99')
79
 * hexColorToRGB('00cc99')
80
 * hexColorToRGB(52377)
81
 * hexColorToRGB(0x00Cc99)
82
 */
83
function hexColorToRGB (hexColor: ColorIntensity): [red: number, green: number, blue: number] {
84
  let red: number, green: number, blue: number
85
  const hasHash = hashRegEx.test(hexColor as string)
86
  const hasHex = hexRegEx.test(hexColor as string)
87
  const color: string = isHexNumber
88
    ? hexColor.toString(16)
89
    : hasHash
90
      ? (hexColor as string).replace(hashRegEx, '')
91
      : hasHex
92
        ? (hexColor as string).replace(hexRegEx, '')
93
        : hexColor as string
94
95
  // Color has only a single char in the last digit, so the last digit must be repeated, and red and green are 0
96
  if (color.length === 1) {
97
    red = 0
98
    green = 0
99
    blue = hexToDec(color.repeat(2))
100
  // Color has two chars in the last digit, so red and green are 0
101
  } else if (color.length === 2) {
102
    red = 0
103
    green = 0
104
    blue = hexToDec(color)
105
  // Color has one chars for each color, so they must be repeated
106
  } else if (color.length === 3) {
107
    red = hexToDec(color[0].repeat(2))
108
    green = hexToDec(color[1].repeat(2))
109
    blue = hexToDec(color[2].repeat(2))
110
  // Color has only for chars, so red is 0
111
  } else if (color.length === 4) {
112
    red = 0
113
    green = hexToDec(color.substr(0, 2))
114
    blue = hexToDec(color.substr(2, 2))
115
  // All chars are filled, so no transformation is needed
116
  } else {
117
    red = hexToDec(color.substr(0, 2))
118
    green = hexToDec(color.substr(2, 2))
119
    blue = hexToDec(color.substr(4, 2))
120
  }
121
122
  return [red, green, blue]
123
}
124
125
/**
126
 * Converts a color array or separated in RGB to the respective RGB values
127
 * @param redOrArray The RGB array or the color red
128
 * @param green The color green
129
 * @param blue The color blue
130
 * @returns The array with the RGB values
131
 * @example All these examples produces the same value
132
 * arrayOrRgbToRGB(0, 0xcc, 153)
133
 * arrayOrRgbToRGB(0x0, 0xcc, 153)
134
 * arrayOrRgbToRGB(0, 204, 0x99)
135
 * arrayOrRgbToRGB([0, 0xcc, 153])
136
 * arrayOrRgbToRGB([0x0, 0xcc, 153])
137
 * arrayOrRgbToRGB([0, 204, 0x99])
138
 */
139
function arrayOrRgbToRGB (redOrArray: number | number[], green?: number, blue?: number): [red: number, green: number, blue: number] {
140
  let r = 0
141
  let g = 0
142
  let b = 0
143
  if (isArray) {
144
    r = (redOrArray as number[])[0]
145
    g = (redOrArray as number[])[1]
146
    b = (redOrArray as number[])[2]
147
  } else if (isRGB) {
148
    r = redOrArray as number
149
    g = green as number
150
    b = blue as number
151
  }
152
153
  return [r, g, b]
154
}
155
156
/**
157
 * Calculates the best color (black or white) to contrast with the passed RGB color using the algorithm from https://alienryderflex.com/hsp.html
158
 * @param red The color red value
159
 * @param green The color green value
160
 * @param blue The color blue value
161
 * @returns Black or White depending on the best possible contrast
162
 */
163
function contrastFromHSP (red: number, green: number, blue: number): '#000000' | '#ffffff' {
164
  const pRed = 0.299
165
  const pGreen = 0.587
166
  const pBlue = 0.114
167
168
  const contrast = Math.sqrt(
169
    pRed * Math.pow((red / 255), 2) +
170
    pGreen * Math.pow((green / 255), 2) +
171
    pBlue * Math.pow((blue / 255), 2)
172
  )
173
174
  return contrast > 0.5
175
    ? '#000000'
176
    : '#ffffff'
177
}
178