mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-10-01 23:58:36 -05:00
Redesign (#1179)
* Remove light mode * Trim header * New front page initial * Get rid of build layout * Breadcrumbs * Desktop side nav * Overhaul colors * Add breadcrumbs * New sub nav style * Front page action buttons * Add back add new build button * Add articles page with icon * Minor Object damage page layout tweaks * Remove one unnecessary render from object damage * Fix wrong link in article page * Profile -> My Page in header * Log in/out buttons in front * Add drawings to front page * Remove unnecessary comment
This commit is contained in:
353
scripts/hex-to-filter.ts
Normal file
353
scripts/hex-to-filter.ts
Normal file
@@ -0,0 +1,353 @@
|
||||
/* eslint-disable */
|
||||
// @ts-nocheck
|
||||
|
||||
export {};
|
||||
|
||||
interface HSL {
|
||||
h: number;
|
||||
s: number;
|
||||
l: number;
|
||||
}
|
||||
|
||||
class Color {
|
||||
public r: number;
|
||||
public g: number;
|
||||
public b: number;
|
||||
constructor(r: number, g: number, b: number) {
|
||||
this.r = this.clamp(r);
|
||||
this.g = this.clamp(g);
|
||||
this.b = this.clamp(b);
|
||||
}
|
||||
|
||||
toString() {
|
||||
return `rgb(${Math.round(this.r)}, ${Math.round(this.g)}, ${Math.round(
|
||||
this.b
|
||||
)})`;
|
||||
}
|
||||
|
||||
set(r: number, g: number, b: number) {
|
||||
this.r = this.clamp(r);
|
||||
this.g = this.clamp(g);
|
||||
this.b = this.clamp(b);
|
||||
}
|
||||
|
||||
hueRotate(angle = 0) {
|
||||
angle = (angle / 180) * Math.PI;
|
||||
const sin = Math.sin(angle);
|
||||
const cos = Math.cos(angle);
|
||||
|
||||
this.multiply([
|
||||
0.213 + cos * 0.787 - sin * 0.213,
|
||||
0.715 - cos * 0.715 - sin * 0.715,
|
||||
0.072 - cos * 0.072 + sin * 0.928,
|
||||
0.213 - cos * 0.213 + sin * 0.143,
|
||||
0.715 + cos * 0.285 + sin * 0.14,
|
||||
0.072 - cos * 0.072 - sin * 0.283,
|
||||
0.213 - cos * 0.213 - sin * 0.787,
|
||||
0.715 - cos * 0.715 + sin * 0.715,
|
||||
0.072 + cos * 0.928 + sin * 0.072,
|
||||
]);
|
||||
}
|
||||
|
||||
grayscale(value = 1) {
|
||||
this.multiply([
|
||||
0.2126 + 0.7874 * (1 - value),
|
||||
0.7152 - 0.7152 * (1 - value),
|
||||
0.0722 - 0.0722 * (1 - value),
|
||||
0.2126 - 0.2126 * (1 - value),
|
||||
0.7152 + 0.2848 * (1 - value),
|
||||
0.0722 - 0.0722 * (1 - value),
|
||||
0.2126 - 0.2126 * (1 - value),
|
||||
0.7152 - 0.7152 * (1 - value),
|
||||
0.0722 + 0.9278 * (1 - value),
|
||||
]);
|
||||
}
|
||||
|
||||
sepia(value = 1) {
|
||||
this.multiply([
|
||||
0.393 + 0.607 * (1 - value),
|
||||
0.769 - 0.769 * (1 - value),
|
||||
0.189 - 0.189 * (1 - value),
|
||||
0.349 - 0.349 * (1 - value),
|
||||
0.686 + 0.314 * (1 - value),
|
||||
0.168 - 0.168 * (1 - value),
|
||||
0.272 - 0.272 * (1 - value),
|
||||
0.534 - 0.534 * (1 - value),
|
||||
0.131 + 0.869 * (1 - value),
|
||||
]);
|
||||
}
|
||||
|
||||
saturate(value = 1) {
|
||||
this.multiply([
|
||||
0.213 + 0.787 * value,
|
||||
0.715 - 0.715 * value,
|
||||
0.072 - 0.072 * value,
|
||||
0.213 - 0.213 * value,
|
||||
0.715 + 0.285 * value,
|
||||
0.072 - 0.072 * value,
|
||||
0.213 - 0.213 * value,
|
||||
0.715 - 0.715 * value,
|
||||
0.072 + 0.928 * value,
|
||||
]);
|
||||
}
|
||||
|
||||
multiply(matrix: any) {
|
||||
const newR = this.clamp(
|
||||
this.r * matrix[0] + this.g * matrix[1] + this.b * matrix[2]
|
||||
);
|
||||
const newG = this.clamp(
|
||||
this.r * matrix[3] + this.g * matrix[4] + this.b * matrix[5]
|
||||
);
|
||||
const newB = this.clamp(
|
||||
this.r * matrix[6] + this.g * matrix[7] + this.b * matrix[8]
|
||||
);
|
||||
this.r = newR;
|
||||
this.g = newG;
|
||||
this.b = newB;
|
||||
}
|
||||
|
||||
brightness(value = 1) {
|
||||
this.linear(value);
|
||||
}
|
||||
contrast(value = 1) {
|
||||
this.linear(value, -(0.5 * value) + 0.5);
|
||||
}
|
||||
|
||||
linear(slope = 1, intercept = 0) {
|
||||
this.r = this.clamp(this.r * slope + intercept * 255);
|
||||
this.g = this.clamp(this.g * slope + intercept * 255);
|
||||
this.b = this.clamp(this.b * slope + intercept * 255);
|
||||
}
|
||||
|
||||
invert(value = 1) {
|
||||
this.r = this.clamp((value + (this.r / 255) * (1 - 2 * value)) * 255);
|
||||
this.g = this.clamp((value + (this.g / 255) * (1 - 2 * value)) * 255);
|
||||
this.b = this.clamp((value + (this.b / 255) * (1 - 2 * value)) * 255);
|
||||
}
|
||||
|
||||
hsl(): HSL {
|
||||
// Code taken from https://stackoverflow.com/a/9493060/2688027, licensed under CC BY-SA.
|
||||
const r = this.r / 255;
|
||||
const g = this.g / 255;
|
||||
const b = this.b / 255;
|
||||
const max = Math.max(r, g, b);
|
||||
const min = Math.min(r, g, b);
|
||||
|
||||
let h = 0;
|
||||
let s = 0;
|
||||
let l = (max + min) / 2;
|
||||
|
||||
if (max === min) {
|
||||
h = s = 0;
|
||||
} else {
|
||||
const d = max - min;
|
||||
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
|
||||
switch (max) {
|
||||
case r:
|
||||
h = (g - b) / d + (g < b ? 6 : 0);
|
||||
break;
|
||||
|
||||
case g:
|
||||
h = (b - r) / d + 2;
|
||||
break;
|
||||
|
||||
case b:
|
||||
h = (r - g) / d + 4;
|
||||
break;
|
||||
}
|
||||
h /= 6;
|
||||
}
|
||||
|
||||
return {
|
||||
h: h * 100,
|
||||
s: s * 100,
|
||||
l: l * 100,
|
||||
};
|
||||
}
|
||||
|
||||
clamp(value: number): number {
|
||||
if (value > 255) {
|
||||
value = 255;
|
||||
} else if (value < 0) {
|
||||
value = 0;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
interface Solution {
|
||||
loss: number;
|
||||
values: number[];
|
||||
}
|
||||
class Solver {
|
||||
private target: Color;
|
||||
private targetHSL: HSL;
|
||||
private reusedColor: Color;
|
||||
constructor(target: Color) {
|
||||
this.target = target;
|
||||
this.targetHSL = target.hsl();
|
||||
this.reusedColor = new Color(0, 0, 0);
|
||||
}
|
||||
|
||||
solve() {
|
||||
const result = this.solveNarrow(this.solveWide());
|
||||
return {
|
||||
values: result.values,
|
||||
loss: result.loss,
|
||||
filter: this.css(result.values),
|
||||
};
|
||||
}
|
||||
|
||||
solveWide(): Solution {
|
||||
const A = 5;
|
||||
const c = 15;
|
||||
const a = [60, 180, 18000, 600, 1.2, 1.2];
|
||||
|
||||
let best = { loss: Infinity, values: [] as number[] };
|
||||
for (let i = 0; best.loss > 25 && i < 3; i++) {
|
||||
const initial = [50, 20, 3750, 50, 100, 100];
|
||||
const result = this.spsa(A, a, c, initial, 1000);
|
||||
if (result.loss < best.loss) {
|
||||
best = result;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
solveNarrow(wide: Solution) {
|
||||
const A = wide.loss;
|
||||
const c = 2;
|
||||
const A1 = A + 1;
|
||||
const a = [0.25 * A1, 0.25 * A1, A1, 0.25 * A1, 0.2 * A1, 0.2 * A1];
|
||||
return this.spsa(A, a, c, wide.values, 500);
|
||||
}
|
||||
|
||||
spsa(
|
||||
A: number,
|
||||
a: number[],
|
||||
c: number,
|
||||
values: number[],
|
||||
iters: number
|
||||
): Solution {
|
||||
const alpha = 1;
|
||||
const gamma = 0.16666666666666666;
|
||||
|
||||
let best = [] as number[];
|
||||
let bestLoss = Infinity;
|
||||
const deltas = new Array(6);
|
||||
const highArgs = new Array(6);
|
||||
const lowArgs = new Array(6);
|
||||
|
||||
for (let k = 0; k < iters; k++) {
|
||||
const ck = c / Math.pow(k + 1, gamma);
|
||||
for (let i = 0; i < 6; i++) {
|
||||
deltas[i] = Math.random() > 0.5 ? 1 : -1;
|
||||
highArgs[i] = values[i] + ck * deltas[i];
|
||||
lowArgs[i] = values[i] - ck * deltas[i];
|
||||
}
|
||||
|
||||
const lossDiff = this.loss(highArgs) - this.loss(lowArgs);
|
||||
for (let i = 0; i < 6; i++) {
|
||||
const g = (lossDiff / (2 * ck)) * deltas[i];
|
||||
const ak = a[i] / Math.pow(A + k + 1, alpha);
|
||||
values[i] = fix(values[i] - ak * g, i);
|
||||
}
|
||||
|
||||
const loss = this.loss(values);
|
||||
if (loss < bestLoss) {
|
||||
best = values.slice(0);
|
||||
bestLoss = loss;
|
||||
}
|
||||
}
|
||||
return { values: best, loss: bestLoss };
|
||||
|
||||
function fix(value: number, idx: number): number {
|
||||
let max = 100;
|
||||
if (idx === 2 /* saturate */) {
|
||||
max = 7500;
|
||||
} else if (idx === 4 /* brightness */ || idx === 5 /* contrast */) {
|
||||
max = 200;
|
||||
}
|
||||
|
||||
if (idx === 3 /* hue-rotate */) {
|
||||
if (value > max) {
|
||||
value %= max;
|
||||
} else if (value < 0) {
|
||||
value = max + (value % max);
|
||||
}
|
||||
} else if (value < 0) {
|
||||
value = 0;
|
||||
} else if (value > max) {
|
||||
value = max;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
loss(filters: number[]) {
|
||||
// Argument is array of percentages.
|
||||
const color = this.reusedColor;
|
||||
color.set(0, 0, 0);
|
||||
|
||||
color.invert(filters[0] / 100);
|
||||
color.sepia(filters[1] / 100);
|
||||
color.saturate(filters[2] / 100);
|
||||
color.hueRotate(filters[3] * 3.6);
|
||||
color.brightness(filters[4] / 100);
|
||||
color.contrast(filters[5] / 100);
|
||||
|
||||
const colorHSL = color.hsl();
|
||||
return (
|
||||
Math.abs(color.r - this.target.r) +
|
||||
Math.abs(color.g - this.target.g) +
|
||||
Math.abs(color.b - this.target.b) +
|
||||
Math.abs(colorHSL.h - this.targetHSL.h) +
|
||||
Math.abs(colorHSL.s - this.targetHSL.s) +
|
||||
Math.abs(colorHSL.l - this.targetHSL.l)
|
||||
);
|
||||
}
|
||||
|
||||
css(filters: number[]) {
|
||||
function fmt(idx: number, multiplier = 1) {
|
||||
return Math.round(filters[idx] * multiplier);
|
||||
}
|
||||
return `invert(${fmt(0)}%) sepia(${fmt(1)}%) saturate(${fmt(
|
||||
2
|
||||
)}%) hue-rotate(${fmt(3, 3.6)}deg) brightness(${fmt(4)}%) contrast(${fmt(
|
||||
5
|
||||
)}%)`;
|
||||
}
|
||||
}
|
||||
|
||||
type RGB = [number, number, number];
|
||||
function hexToRgb(hex: string): RGB {
|
||||
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
|
||||
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
|
||||
hex = hex.replace(shorthandRegex, (_m, r, g, b) => {
|
||||
return r + r + g + g + b + b;
|
||||
});
|
||||
|
||||
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
|
||||
if (result) {
|
||||
return [
|
||||
parseInt(result[1], 16),
|
||||
parseInt(result[2], 16),
|
||||
parseInt(result[3], 16),
|
||||
];
|
||||
}
|
||||
|
||||
throw new Error("Error parsing hex: " + hex);
|
||||
}
|
||||
|
||||
function hexToFilter(hex: string) {
|
||||
let rgb = [255, 255, 255];
|
||||
try {
|
||||
rgb = hexToRgb(hex);
|
||||
} catch (e) {}
|
||||
const color = new Color(rgb[0], rgb[1], rgb[2]);
|
||||
const solver = new Solver(color);
|
||||
const result = solver.solve();
|
||||
console.log(" --- RESULT ---");
|
||||
console.log(result.filter);
|
||||
}
|
||||
|
||||
hexToFilter(process.argv[2]?.trim());
|
||||
Reference in New Issue
Block a user