PageSourceSearch

https://xyztype.com/assets/gray_matter/utility/color-419784ecb96cb…7bd97460278b896db3ff1aff0d778222a89fce6b.js

js xyztype.com collected 2026-09-25 04:26:25 UTC 6,412 bytes, 226 lines download raw bytes

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.