1function rgbToHex(r, g, b) { 2 function componentToHex(c) { 3 var hex = c.toString(16); 4 return hex.length == 1 ? "0" + hex : hex; 5 } 6 7 return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b); 8 } 9 10 function hexToRgb(hex) { 11 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF") 12 const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i; 13 hex = hex.replace(shorthandRegex, (m, r, g, b) => { 14 return r + r + g + g + b + b; 15 }); 16 17 const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex); 18 return result 19 ? [ 20 parseInt(result[1], 16), 21 parseInt(result[2], 16), 22 parseInt(result[3], 16), 23 ] 24 : null; 25 } 26 27 class Color { 28 constructor(r, g, b) { 29 this.set(r, g, b); 30 } 31 32 toRgb() { 33 return `rgb(${Math.round(this.r)}, ${Math.round(this.g)}, ${Math.round(this.b)})`; 34 } 35 36 toHex() { 37 return rgbToHex(Math.round(this.r), Math.round(this.g), Math.round(this.b)); 38 } 39 40 set(r, g, b) { 41 this.r = this.clamp(r); 42 this.g = this.clamp(g); 43 this.b = this.clamp(b); 44 } 45 46 hueRotate(angle = 0) { 47 angle = angle / 180 * Math.PI; 48 const sin = Math.sin(angle); 49 const cos = Math.cos(angle); 50 51 this.multiply([ 52 0.213 + cos * 0.787 - sin * 0.213, 53 0.715 - cos * 0.715 - sin * 0.715, 54 0.072 - cos * 0.072 + sin * 0.928, 55 0.213 - cos * 0.213 + sin * 0.143, 56 0.715 + cos * 0.285 + sin * 0.140, 57 0.072 - cos * 0.072 - sin * 0.283, 58 0.213 - cos * 0.213 - sin * 0.787, 59 0.715 - cos * 0.715 + sin * 0.715, 60 0.072 + cos * 0.928 + sin * 0.072, 61 ]); 62 } 63 64 grayscale(value = 1) { 65 this.multiply([ 66 0.2126 + 0.7874 * (1 - value), 67 0.7152 - 0.7152 * (1 - value), 68 0.0722 - 0.0722 * (1 - value), 69 0.2126 - 0.2126 * (1 - value), 70 0.7152 + 0.2848 * (1 - value), 71 0.0722 - 0.0722 * (1 - value), 72 0.2126 - 0.2126 * (1 - value), 73 0.7152 - 0.7152 * (1 - value), 74 0.0722 + 0.9278 * (1 - value), 75 ]); 76 } 77 78 sepia(value = 1) { 79 this.multiply([ 80 0.393 + 0.607 * (1 - value), 81 0.769 - 0.769 * (1 - value), 82 0.189 - 0.189 * (1 - value), 83 0.349 - 0.349 * (1 - value), 84 0.686 + 0.314 * (1 - value), 85 0.168 - 0.168 * (1 - value), 86 0.272 - 0.272 * (1 - value), 87 0.534 - 0.534 * (1 - value), 88 0.131 + 0.869 * (1 - value), 89 ]); 90 } 91 92 saturate(value = 1) { 93 this.multiply([ 94 0.213 + 0.787 * value, 95 0.715 - 0.715 * value, 96 0.072 - 0.072 * value, 97 0.213 - 0.213 * value, 98 0.715 + 0.285 * value, 99 0.072 - 0.072 * value, 100 0.213 - 0.213 * value, 101 0.715 - 0.715 * value, 102 0.072 + 0.928 * value, 103 ]); 104 } 105 106 multiply(matrix) { 107 const newR = this.clamp(this.r * matrix[0] + this.g * matrix[1] + this.b * matrix[2]); 108 const newG = this.clamp(this.r * matrix[3] + this.g * matrix[4] + this.b * matrix[5]); 109 const newB = this.clamp(this.r * matrix[6] + this.g * matrix[7] + this.b * matrix[8]); 110 this.r = newR; 111 this.g = newG; 112 this.b = newB; 113 } 114 115 brightness(value = 1) { 116 this.linear(value); 117 } 118 contrast(value = 1) { 119 this.linear(value, -(0.5 * value) + 0.5); 120 } 121 122 linear(slope = 1, intercept = 0) { 123 this.r = this.clamp(this.r * slope + intercept * 255); 124 this.g = this.clamp(this.g * slope + intercept * 255); 125 this.b = this.clamp(this.b * slope + intercept * 255); 126 } 127 128 invert(value = 1) { 129 this.r = this.clamp((value + this.r / 255 * (1 - 2 * value)) * 255); 130 this.g = this.clamp((value + this.g / 255 * (1 - 2 * value)) * 255); 131 this.b = this.clamp((value + this.b / 255 * (1 - 2 * value)) * 255); 132 } 133 134 hsl() { 135 // Code taken from https://stackoverflow.com/a/9493060/2688027, licensed under CC BY-SA. 136 const r = this.r / 255; 137 const g = this.g / 255; 138 const b = this.b / 255; 139 const max = Math.max(r, g, b); 140 const min = Math.min(r, g, b); 141 let h, s, l = (max + min) / 2; 142
143 if (max === min) { 144 h = s = 0; 145 } else { 146 const d = max - min; 147 s = l > 0.5 ? d / (2 - max - min) : d / (max + min); 148 switch (max) { 149 case r: 150 h = (g - b) / d + (g < b ? 6 : 0); 151 break; 152 153 case g: 154 h = (b - r) / d + 2; 155 break; 156 157 case b: 158 h = (r - g) / d + 4; 159 break; 160 } 161 h /= 6; 162 } 163 164 return { 165 h: h * 100, 166 s: s * 100, 167 l: l * 100, 168 }; 169 } 170 171 clamp(value) { 172 if (value > 255) { 173 value = 255; 174 } else if (value < 0) { 175 value = 0; 176 } 177 return value; 178 } 179 } 180 181 class Solver { 182 constructor(target, baseColor) { 183 this.target = target; 184 this.targetHSL = target.hsl(); 185 this.reusedColor = new Color(0, 0, 0); 186 } 187 188 solve() { 189 const result = this.solveNarrow(this.solveWide()); 190 return { 191 values: result.values, 192 loss: result.loss, 193 filter: this.css(result.values), 194 filterRaw: this.raw(result.values), 195 }; 196 } 197 198 solveWide() { 199 const A = 5; 200 const c = 15; 201 const a = [60, 180, 18000, 600, 1.2, 1.2]; 202 203 let best = { loss: Infinity }; 204 for (let i = 0; best.loss > 25 && i < 3; i++) { 205 const initial = [50, 20, 3750, 50, 100, 100]; 206 const result = this.spsa(A, a, c, initial, 1000); 207 if (result.loss < best.loss) { 208 best = result; 209 } 210 } 211 return best; 212 } 213 214 solveNarrow(wide) { 215 const A = wide.loss; 216 const c = 2; 217 const A1 = A + 1; 218 const a = [0.25 * A1, 0.25 * A1, A1, 0.25 * A1, 0.2 * A1, 0.2 * A1]; 219 return this.spsa(A, a, c, wide.values, 500); 220 } 221 222 spsa(A, a, c, values, iters) { 223 const alpha = 1; 224 const gamma = 0.16666666666666666; 225 226 let best = null; 227 let bestLoss = Infinity; 228 const deltas = new Array(6); 229 const highArgs = new Array(6); 230 const lowArgs = new Array(6); 231 232 for (let k = 0; k < iters; k++) { 233 const ck = c / Math.pow(k + 1, gamma); 234 for (let i = 0; i < 6; i++) { 235 deltas[i] = Math.random() > 0.5 ? 1 : -1; 236 highArgs[i] = values[i] + ck * deltas[i]; 237 lowArgs[i] = values[i] - ck * deltas[i]; 238 } 239 240 const lossDiff = this.loss(highArgs) - this.loss(lowArgs); 241 for (let i = 0; i < 6; i++) { 242 const g = lossDiff / (2 * ck) * deltas[i]; 243 const ak = a[i] / Math.pow(A + k + 1, alpha); 244 values[i] = fix(values[i] - ak * g, i); 245 } 246 247 const loss = this.loss(values); 248 if (loss < bestLoss) { 249 best = values.slice(0); 250 bestLoss = loss; 251 } 252 } 253 return { values: best, loss: bestLoss }; 254 255 function fix(value, idx) { 256 let max = 100; 257 if (idx === 2 /* saturate */) { 258 max = 7500; 259 } else if (idx === 4 /* brightness */ || idx === 5 /* contrast */) { 260 max = 200; 261 } 262 263 if (idx === 3 /* hue-rotate */) { 264 if (value > max) { 265 value %= max; 266 } else if (value < 0) { 267 value = max + value % max; 268 } 269 } else if (value < 0) { 270 value = 0; 271 } else if (value > max) { 272 value = max; 273 } 274 return value; 275 } 276 } 277 278 loss(filters) { 279 // Argument is array of percentages. 280 const color = this.reusedColor; 281 color.set(0, 0, 0); 282 283 color.invert(filters[0] / 100); 284 color.sepia(filters[1] / 100); 285 color.saturate(filters[2] / 100); 286 color.hueRotate(filters[3] * 3.6); 287 color.brightness(filters[4] / 100); 288 color.contrast(filters[5] / 100); 289 290 const colorHSL = color.hsl(); 291 return ( 292 Math.abs(color.r - this.target.r) + 293 Math.abs(color.g - this.target.g) + 294 Math.abs(color.b - this.target.b) + 295 Math.abs(colorHSL.h - this.targetHSL.h) + 296 Math.abs(colorHSL.s - this.targetHSL.s) + 297 Math.abs(colorHSL.l - this.targetHSL.l) 298 ); 299 } 300 301 raw(filters) { 302 function fmt(idx, multiplier = 1) { 303 return Math.round(filters[idx] * multiplier); 304 } 305 return `brightness(0) saturate(100%) invert(${fmt(0)}%) sepia(${fmt(1)}%) saturate(${fmt(2)}%) hue-rotate(${fmt(3, 3.6)}deg) brightness(${fmt(4)}%) contrast(${fmt(5)}%)`; 306 } 307 308 css(filters) { 309 function fmt(idx, multiplier = 1) { 310 return Math.round(filters[idx] * multiplier); 311 } 312 return `filter: brightness(0) saturate(100%) invert(${fmt(0)}%) sepia(${fmt(1)}%) saturate(${fmt(2)}%) hue-rotate(${fmt(3, 3.6)}deg) brightness(${fmt(4)}%) contrast(${fmt(5)}%);`; 313 } 314 } 315 316 function computeColourFilter(rgb) { 317 318 if (rgb.length !== 3) { 319 alert('Invalid format!'); 320 return; 321 } 322 323 const color = new Color(rgb[0], rgb[1], rgb[2]); 324 const solver = new Solver(color); 325 const result = solver.solve(); 326 let lossMsg = ''; 327 const res = { 328 color, 329 solver, 330 result, 331 lossMsg, 332 }; 333 334 return result; 335 } 336 337 function validateColor(color) { 338 const submitButton = document.getElementById('action-button'); 339 const HEXColorRegExp = /^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/; 340 const isValid = HEXColorRegExp.test(color); 341 342 if (isValid) { 343 submitButton.classList.remove('disabled'); 344 } else if (!submitButton.classList.contains('disabled')) { 345 submitButton.classList.add('disabled'); 346 } 347 } 348 349 function coloursOnStart(){ 350 let rgb = $('.mobileOpen .links a, .mobileSubnav .links a ').css('color'); 351 rgb = rgb.replace('rgb(', "").replace(')', ''); 352 rgb = rgb.split(', '); 353 result = computeColourFilter(rgb).filter.replace(';', ' !important;'); 354 355 $('head').append('<style>.mobileNavigation .content img {' + result + '}'); 356 357 let rgb_links = $('.navigation .linksContain a').css('color'); 358 rgb_links = rgb_links.replace('rgb(', "").replace(')', ''); 359 rgb_links = rgb_links.split(', '); 360 result = computeColourFilter(rgb_links).filter.replace(';', ' !important;'); 361 362 $('head').append('<style>.navigation .linksContain .dropdown-toggle::after{' + result + '}'); 363 } 364 365 coloursOnStart();
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.