1// Copyright (c) 2021-2025 GrayBits LLC 2 3export default class Color{ 4 constructor(color){ 5 if (color.hex){ 6 this.setHex(color.hex); 7 } 8 if (color.rgb){ 9 this.setRGB(color.rgb); 10 } 11 if (color.hsl){ 12 this.setHSL(color.hsl); 13 } 14 if (color.hsv){ 15 this.setHSV(color.hsv); 16 } 17 } 18 19 setHex(hex){ 20 this.hsv = this.roundTriplet(Color.hsvFromHex(hex)); 21 this.hsl = this.roundTriplet(Color.hslFromHex(hex)); 22 this.rgb = this.roundTriplet(Color.rgbFromHex(hex)); 23 this.hex = hex; 24 } 25 26 setRGB(rgb){ 27 this.hsv = this.roundTriplet(Color.hsvFromRGB(rgb)); 28 this.hsl = this.roundTriplet(Color.hslFromRGB(rgb)); 29 this.rgb = this.roundTriplet(rgb); 30 this.hex = Color.hexFromRGB(rgb); 31 } 32 33 setHSL(hsl){ 34 this.hsv = this.roundTriplet(Color.hsvFromHSL(hsl)); 35 this.hsl = this.roundTriplet(hsl); 36 this.rgb = this.roundTriplet(Color.rgbFromHSL(hsl)); 37 this.hex = Color.hexFromHSL(hsl); 38 } 39 40 setHSV(hsv){ 41 this.hsv = this.roundTriplet(hsv); 42 this.hsl = this.roundTriplet(Color.hslFromHSV(hsv)) 43 this.rgb = this.roundTriplet(Color.rgbFromHSV(hsv)); 44 this.hex = Color.hexFromHSV(hsv); 45 } 46 47 contrast(contrasting_color){ 48 let l1 = Color.relativeLuminance(this.rgb) + 0.05; 49 let l2 = Color.relativeLuminance(contrasting_color.rgb) + 0.05; 50 return Math.max(l1 / l2, l2 / l1); 51 } 52 53 roundTriplet(triplet){ 54 return [Math.round(triplet[0]), Math.round(triplet[1]), Math.round(triplet[2])]; 55 } 56 57 static rgbFromHSL(hsl_in){ 58 let c = (1 - Math.abs(2 * hsl_in[2] / 100 - 1)) * hsl_in[1] / 100; 59 let x = c * (1 - Math.abs((hsl_in[0] / 60) % 2 - 1)); 60 let m = hsl_in[2] / 100 - c / 2; 61 let h = hsl_in[0]; 62 63 if (h >= 0 && h < 60){ return Color.rgbPrimeToRGB(c, x, 0, m); } 64 if (h >= 60 && h < 120){ return Color.rgbPrimeToRGB(x, c, 0, m); } 65 if (h >= 120 && h < 180){ return Color.rgbPrimeToRGB(0, c, x, m); } 66 if (h >= 180 && h < 240){ return Color.rgbPrimeToRGB(0, x, c, m); } 67 if (h >= 240 && h < 300){ return Color.rgbPrimeToRGB(x, 0, c, m); } 68 if (h >= 300 && h < 360){ return Color.rgbPrimeToRGB(c, 0, x, m); } 69 } 70 71 static rgbFromHSV(hsv_in){ 72 let c = (hsv_in[1] / 100) * (hsv_in[2] / 100); 73 let x = c * (1 - Math.abs((hsv_in[0] / 60) % 2 - 1)); 74 let m = hsv_in[2] / 100 - c; 75 let h = hsv_in[0]; 76 77 if (h >= 0 && h < 60){ return Color.rgbPrimeToRGB(c, x, 0, m); } 78 if (h >= 60 && h < 120){ return Color.rgbPrimeToRGB(x, c, 0, m); } 79 if (h >= 120 && h < 180){ return Color.rgbPrimeToRGB(0, c, x, m); } 80 if (h >= 180 && h < 240){ return Color.rgbPrimeToRGB(0, x, c, m); } 81 if (h >= 240 && h < 300){ return Color.rgbPrimeToRGB(x, 0, c, m); } 82 if (h >= 300 && h < 360){ return Color.rgbPrimeToRGB(c, 0, x, m); } 83 } 84 85 static rgbPrimeToRGB(r, g, b, m){ 86 return [(r + m) * 255, (g + m) * 255, (b + m) * 255]; 87 } 88 89 static rgbFromHex(hex_in){ 90 return [parseInt("0x" + hex_in.substring(0,2)), parseInt("0x" + hex_in.substring(2,4)), parseInt("0x" + hex_in.substring(4,6))]; 91 } 92 93 static hexFromRGB(rgb_in){ 94 return Color.toHex(rgb_in[0]) + Color.toHex(rgb_in[1]) + Color.toHex(rgb_in[2]); 95 } 96 97 static hexFromHSL(hsl_in){ 98 return Color.hexFromRGB(Color.rgbFromHSL(hsl_in)); 99 } 100 101 static hexFromHSV(hsv_in){ 102 return Color.hexFromRGB(Color.rgbFromHSV(hsv_in)); 103 } 104 105 static hslFromRGB(rgb_in){ 106 let h, s, l; 107 let r = rgb_in[0] / 255; 108 let g = rgb_in[1] / 255; 109 let b = rgb_in[2] / 255; 110 let c_max = Math.max(r, g, b); 111 let c_min = Math.min(r, g, b); 112 let delta = c_max - c_min; 113 114 l = 0.5 * (c_max + c_min); 115 if (delta == 0){ 116 h = 0; 117 s = 0; 118 } else { 119 if (c_max == r){ 120 h = 60 * ((g - b) / delta); 121 } 122 if (c_max == g){ 123 h = 60 * (((b - r) / delta) + 2); 124 } 125 if (c_max == b){ 126 h = 60 * (((r - g) / delta) + 4); 127 } 128 h = (h + 360) % 360; 129 s = delta / (1 - Math.abs(2 * l - 1)); 130 } 131 return [h, 100 * s, 100 * l]; 132 } 133 134 static hslFromHex(hex_in){ 135 return Color.hslFromRGB(Color.rgbFromHex(hex_in)); 136 } 137 138 static hslFromHSV(hsv_in){ 139 let v = hsv_in[2] * 0.01; 140 let l = v * (1 - hsv_in[1] * 0.005); 141 let s = l == 0 || l == 1 ? 0 : (v - l) / Math.min(l, 1 - l); 142 return [hsv_in[0], 100 * s, 100 * l]; 143 } 144 145 static hsvFromRGB(rgb_in){ 146 let h, s, v; 147 let r = rgb_in[0] / 255; 148 let g = rgb_in[1] / 255; 149 let b = rgb_in[2] / 255;
150 let c_max = Math.max(r, g, b); 151 let c_min = Math.min(r, g, b); 152 let delta = c_max - c_min; 153 154 v = c_max; 155 if (delta == 0){ 156 h = 0; 157 } else { 158 if (c_max == r){ 159 h = 60 * ((g - b) / delta); 160 } 161 if (c_max == g){ 162 h = 60 * (((b - r) / delta) + 2); 163 } 164 if (c_max == b){ 165 h = 60 * (((r - g) / delta) + 4); 166 } 167 } 168 h = (h + 360) % 360; 169 if (c_max == 0){ 170 s = 0; 171 } else { 172 s = delta / c_max; 173 } 174 175 return [h, 100 * s, 100 * v]; 176 } 177 178 static hsvFromHex(hex_in){ 179 return Color.hsvFromRGB(Color.rgbFromHex(hex_in)); 180 } 181 182 static hsvFromHSL(hsl_in){ 183 let l = hsl_in[2] * 0.01; 184 let v = l + 0.01 * hsl_in[1] * Math.min(l, 1 - l); 185 let s = v == 0 ? 0 : 2 * (1 - l / v); 186 187 return [hsl_in[0], 100 * s, 100 * v]; 188 } 189 190 static toHex(n){ 191 let v = Math.round(n).toString(16); 192 if (v.length == 1){ 193 return '0' + v; 194 } else { 195 return v; 196 } 197 } 198 199 static validHex(hex){ 200 hex = hex.toLowerCase().replace(/[^a-f0-9]/g,''); 201 if (hex.length > 6){ 202 hex = hex.substring(0,6); 203 } 204 if (hex.length == 6){ 205 return hex; 206 } else { 207 return false; 208 } 209 } 210 // https://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef 211 // https://ux.stackexchange.com/questions/107318/formula-for-color-contrast-between-text-and-background 212 // The contrast ratio is calculated as (L1 + 0.05) / (L2 + 0.05), where 213 // L1 is the: relative luminance of the lighter of the colors, and 214 // L2 is the relative luminance of the darker of the colors. 215 static relativeLuminance(rgb){ 216 let r = rgb[0] / 255; 217 let g = rgb[1] / 255; 218 let b = rgb[2] / 255; 219 220 r = r <= 0.03928 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4); 221 g = g <= 0.03928 ? g / 12.92 : Math.pow((g + 0.055) / 1.055, 2.4); 222 b = b <= 0.03928 ? b / 12.92 : Math.pow((b + 0.055) / 1.055, 2.4); 223 224 return (0.2126 * r + 0.7152 * g + 0.0722 * b); 225 } 226};
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.