An ATproto social media client -- with an independent Appview.

feat: color representation conversion

serenity 764b0e51 19e4ea2c

+220
+220
src/alf/util/colors/converstion.ts
··· 1 + export interface HslColor { 2 + h: number 3 + s: number 4 + l: number 5 + } 6 + 7 + export interface RgbColor { 8 + r: number 9 + g: number 10 + b: number 11 + } 12 + 13 + export type HexCode = `#${string}` | string 14 + 15 + /** 16 + * Converts a hexcode string in the format `"#RRGGBB"` to a `HslColor` object (`{ h: number, s: number, l: number }`). 17 + * @param {HexCode} hex - A hexcode string in the format `#RRGGBB`. The leading "#" symbol is optional. 18 + * @returns{HslColor} A HSL colour object. 19 + */ 20 + export const hexToHsl = (hex: HexCode): HslColor => { 21 + hex = hex.replace('#', '') 22 + 23 + const r = parseInt(hex.substring(0, 2), 16) / 255 24 + const g = parseInt(hex.substring(2, 4), 16) / 255 25 + const b = parseInt(hex.substring(4, 6), 16) / 255 26 + 27 + const max = Math.max(r, g, b) 28 + const min = Math.min(r, g, b) 29 + const diff = max - min 30 + 31 + let h = 0, 32 + s, 33 + l 34 + 35 + l = (max + min) / 2 36 + 37 + if (diff === 0) { 38 + h = s = 0 39 + } else { 40 + s = l > 0.5 ? diff / (2 - max - min) : diff / (max + min) 41 + 42 + switch (max) { 43 + case r: 44 + h = (g - b) / diff + (g < b ? 6 : 0) 45 + break 46 + case g: 47 + h = (b - r) / diff + 2 48 + break 49 + case b: 50 + h = (r - g) / diff + 4 51 + break 52 + } 53 + h /= 6 54 + } 55 + 56 + return { 57 + h: h * 360, 58 + s: s * 100, 59 + l: l * 100, 60 + } 61 + } 62 + 63 + /** 64 + * Converts a `HslColor` object (`{ h: number, s: number, l: number }`) to a hexcode string in the format `"#RRGGBB"`. 65 + * @param {HslColor} hsl - A HSL colour object. 66 + * @param {boolean} appendSymbol - Whether to append "#" to the start of the hex string. Defaults to true. 67 + * @returns {HexCode} A hexcode string in the format `"#RRGGBB"`. The leading "#" symbol is optional. 68 + */ 69 + export const hslToHex = ( 70 + {h, s, l}: HslColor, 71 + appendSymbol: boolean = true, 72 + ): HexCode => { 73 + h = (h % 360) / 360 74 + s = Math.max(0, Math.min(1, s / 100)) 75 + l = Math.max(0, Math.min(1, l / 100)) 76 + 77 + let m2: number, m1: number 78 + 79 + if (l <= 0.5) { 80 + m2 = l * (s + 1) 81 + } else { 82 + m2 = l + s - l * s 83 + } 84 + 85 + m1 = l * 2 - m2 86 + 87 + function hue(hueValue: number) { 88 + hueValue = 89 + hueValue < 0 ? hueValue + 1 : hueValue > 1 ? hueValue - 1 : hueValue 90 + 91 + if (hueValue * 6 < 1) { 92 + return m1 + (m2 - m1) * hueValue * 6 93 + } else if (hueValue * 2 < 1) { 94 + return m2 95 + } else if (hueValue * 3 < 2) { 96 + return m1 + (m2 - m1) * (2 / 3 - hueValue) * 6 97 + } else { 98 + return m1 99 + } 100 + } 101 + 102 + const r = hue(h + 1 / 3) * 255 103 + const g = hue(h) * 255 104 + const b = hue(h - 1 / 3) * 255 105 + 106 + return `${appendSymbol ? '#' : ''}${r.toString(16)}${g.toString(16)}${b.toString(16)}` 107 + } 108 + 109 + /** 110 + * Converts an `RgbColor` object (`{ r: number, g: number, b: number }`) to a hexcode string in the format `"#RRGGBB"`. 111 + * @param {RgbColor} rgb - An RGB colour object. 112 + * @param {boolean} appendSymbol - Whether to append "#" to the start of the hex string. Defaults to true. 113 + * @returns {HexCode} A hexcode string in the format `"#RRGGBB"`. The leading "#" symbol is optional. 114 + */ 115 + export const rgbToHex = ( 116 + {r, g, b}: RgbColor, 117 + appendSymbol: boolean = true, 118 + ): HexCode => { 119 + return `${appendSymbol ? '#' : ''}${r.toString(16)}${g.toString(16)}${b.toString(16)}` 120 + } 121 + 122 + /** 123 + * Converts a hexcode string in the format `"#RRGGBB"` to an `RgbColor` object (`{ r: number, g: number, b: number }`). 124 + * @param {HexCode} hex - A hexcode string in the format `#RRGGBB`. The leading "#" symbol is optional. 125 + * @returns {RgbColor} An RGB colour object. 126 + */ 127 + export const hexToRgb = (hex: HexCode): RgbColor => { 128 + hex = hex.replace('#', '') 129 + const r = parseInt(hex.substring(0, 2), 16) / 255 130 + const g = parseInt(hex.substring(2, 4), 16) / 255 131 + const b = parseInt(hex.substring(4, 6), 16) / 255 132 + return {r, g, b} 133 + } 134 + 135 + /** 136 + * Converts an `RgbColor` object (`{ r: number, g: number, b: number }`) to a `HslColor` object (`{ h: number, s: number, l: number }`) 137 + * @param {RgbColor} - An RGB colour object. 138 + * @returns {HslColor} A HSL colour object. 139 + */ 140 + export const rgbToHsl = ({r, g, b}: RgbColor): HslColor => { 141 + r = r / 255 142 + g = g / 255 143 + b = b / 255 144 + const max = Math.max(r, g, b) 145 + const min = Math.min(r, g, b) 146 + const diff = max - min 147 + 148 + let h = 0, 149 + s, 150 + l 151 + 152 + l = (max + min) / 2 153 + 154 + if (diff === 0) { 155 + h = s = 0 156 + } else { 157 + s = l > 0.5 ? diff / (2 - max - min) : diff / (max + min) 158 + 159 + switch (max) { 160 + case r: 161 + h = (g - b) / diff + (g < b ? 6 : 0) 162 + break 163 + case g: 164 + h = (b - r) / diff + 2 165 + break 166 + case b: 167 + h = (r - g) / diff + 4 168 + break 169 + } 170 + h /= 6 171 + } 172 + 173 + return { 174 + h: h * 360, 175 + s: s * 100, 176 + l: l * 100, 177 + } 178 + } 179 + 180 + /** 181 + * Converts a `HslColor` object (`{ h: number, s: number, l: number }`) to a `RgbColor` object (`{ r: number, g: number, b: number }`) 182 + * @param {HslColor} - A HSL colour object. 183 + * @returns {RgbColor} An RGB colour object. 184 + */ 185 + export const hslToRgb = ({h, s, l}: HslColor): RgbColor => { 186 + h = (h % 360) / 360 187 + s = Math.max(0, Math.min(1, s / 100)) 188 + l = Math.max(0, Math.min(1, l / 100)) 189 + 190 + let m2: number, m1: number 191 + 192 + if (l <= 0.5) { 193 + m2 = l * (s + 1) 194 + } else { 195 + m2 = l + s - l * s 196 + } 197 + 198 + m1 = l * 2 - m2 199 + 200 + function hue(hueValue: number) { 201 + hueValue = 202 + hueValue < 0 ? hueValue + 1 : hueValue > 1 ? hueValue - 1 : hueValue 203 + 204 + if (hueValue * 6 < 1) { 205 + return m1 + (m2 - m1) * hueValue * 6 206 + } else if (hueValue * 2 < 1) { 207 + return m2 208 + } else if (hueValue * 3 < 2) { 209 + return m1 + (m2 - m1) * (2 / 3 - hueValue) * 6 210 + } else { 211 + return m1 212 + } 213 + } 214 + 215 + const r = hue(h + 1 / 3) * 255 216 + const g = hue(h) * 255 217 + const b = hue(h - 1 / 3) * 255 218 + 219 + return {r: Math.round(r), g: Math.round(g), b: Math.round(b)} 220 + }