aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSam Chudnick <sam@chudnick.com>2023-06-10 06:32:02 -0400
committerSam Chudnick <sam@chudnick.com>2023-06-10 06:32:02 -0400
commitf9333c86086aacd6dc89fbb6a29aac90c4089024 (patch)
treededc63eb09dcea06bb092a19e810fcf485a60042
parent80e5ebc04346b1aba37158dedbdd4045c0a7c042 (diff)
Remove temporary js file
-rw-r--r--src/static/temp.js325
1 files changed, 0 insertions, 325 deletions
diff --git a/src/static/temp.js b/src/static/temp.js
deleted file mode 100644
index 6eeeba7..0000000
--- a/src/static/temp.js
+++ /dev/null
@@ -1,325 +0,0 @@
1e strict';
2
3class Color {
4 constructor(r, g, b) {
5 this.set(r, g, b);
6 }
7
8 toString() {
9 return `rgb(${Math.round(this.r)}, ${Math.round(this.g)}, ${Math.round(this.b)})`;
10 }
11
12 set(r, g, b) {
13 this.r = this.clamp(r);
14 this.g = this.clamp(g);
15 this.b = this.clamp(b);
16 }
17
18 hueRotate(angle = 0) {
19 angle = angle / 180 * Math.PI;
20 const sin = Math.sin(angle);
21 const cos = Math.cos(angle);
22
23 this.multiply([
24 0.213 + cos * 0.787 - sin * 0.213,
25 0.715 - cos * 0.715 - sin * 0.715,
26 0.072 - cos * 0.072 + sin * 0.928,
27 0.213 - cos * 0.213 + sin * 0.143,
28 0.715 + cos * 0.285 + sin * 0.140,
29 0.072 - cos * 0.072 - sin * 0.283,
30 0.213 - cos * 0.213 - sin * 0.787,
31 0.715 - cos * 0.715 + sin * 0.715,
32 0.072 + cos * 0.928 + sin * 0.072,
33 ]);
34 }
35
36 grayscale(value = 1) {
37 this.multiply([
38 0.2126 + 0.7874 * (1 - value),
39 0.7152 - 0.7152 * (1 - value),
40 0.0722 - 0.0722 * (1 - value),
41 0.2126 - 0.2126 * (1 - value),
42 0.7152 + 0.2848 * (1 - value),
43 0.0722 - 0.0722 * (1 - value),
44 0.2126 - 0.2126 * (1 - value),
45 0.7152 - 0.7152 * (1 - value),
46 0.0722 + 0.9278 * (1 - value),
47 ]);
48 }
49
50 sepia(value = 1) {
51 this.multiply([
52 0.393 + 0.607 * (1 - value),
53 0.769 - 0.769 * (1 - value),
54 0.189 - 0.189 * (1 - value),
55 0.349 - 0.349 * (1 - value),
56 0.686 + 0.314 * (1 - value),
57 0.168 - 0.168 * (1 - value),
58 0.272 - 0.272 * (1 - value),
59 0.534 - 0.534 * (1 - value),
60 0.131 + 0.869 * (1 - value),
61 ]);
62 }
63
64 saturate(value = 1) {
65 this.multiply([
66 0.213 + 0.787 * value,
67 0.715 - 0.715 * value,
68 0.072 - 0.072 * value,
69 0.213 - 0.213 * value,
70 0.715 + 0.285 * value,
71 0.072 - 0.072 * value,
72 0.213 - 0.213 * value,
73 0.715 - 0.715 * value,
74 0.072 + 0.928 * value,
75 ]);
76 }
77
78 multiply(matrix) {
79 const newR = this.clamp(this.r * matrix[0] + this.g * matrix[1] + this.b * matrix[2]);
80 const newG = this.clamp(this.r * matrix[3] + this.g * matrix[4] + this.b * matrix[5]);
81 const newB = this.clamp(this.r * matrix[6] + this.g * matrix[7] + this.b * matrix[8]);
82 this.r = newR;
83 this.g = newG;
84 this.b = newB;
85 }
86
87 brightness(value = 1) {
88 this.linear(value);
89 }
90 contrast(value = 1) {
91 this.linear(value, -(0.5 * value) + 0.5);
92 }
93
94 linear(slope = 1, intercept = 0) {
95 this.r = this.clamp(this.r * slope + intercept * 255);
96 this.g = this.clamp(this.g * slope + intercept * 255);
97 this.b = this.clamp(this.b * slope + intercept * 255);
98 }
99
100 invert(value = 1) {
101 this.r = this.clamp((value + this.r / 255 * (1 - 2 * value)) * 255);
102 this.g = this.clamp((value + this.g / 255 * (1 - 2 * value)) * 255);
103 this.b = this.clamp((value + this.b / 255 * (1 - 2 * value)) * 255);
104 }
105
106 hsl() {
107 const r = this.r / 255;
108 const g = this.g / 255;
109 const b = this.b / 255;
110 const max = Math.max(r, g, b);
111 const min = Math.min(r, g, b);
112 let h, s, l = (max + min) / 2;
113
114 if (max === min) {
115 h = s = 0;
116 } else {
117 const d = max - min;
118 s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
119 switch (max) {
120 case r:
121 h = (g - b) / d + (g < b ? 6 : 0);
122 break;
123
124 case g:
125 h = (b - r) / d + 2;
126 break;
127
128 case b:
129 h = (r - g) / d + 4;
130 break;
131 }
132 h /= 6;
133 }
134
135 return {
136 h: h * 100,
137 s: s * 100,
138 l: l * 100,
139 };
140 }
141
142 clamp(value) {
143 if (value > 255) {
144 value = 255;
145 } else if (value < 0) {
146 value = 0;
147 }
148 return value;
149 }
150}
151
152class Solver {
153 constructor(target, baseColor) {
154 this.target = target;
155 this.targetHSL = target.hsl();
156 this.reusedColor = new Color(0, 0, 0);
157 }
158
159 solve() {
160 const result = this.solveNarrow(this.solveWide());
161 return {
162 values: result.values,
163 loss: result.loss,
164 filter: this.css(result.values),
165 };
166 }
167
168 solveWide() {
169 const A = 5;
170 const c = 15;
171 const a = [60, 180, 18000, 600, 1.2, 1.2];
172
173 let best = { loss: Infinity };
174 for (let i = 0; best.loss > 25 && i < 3; i++) {
175 const initial = [50, 20, 3750, 50, 100, 100];
176 const result = this.spsa(A, a, c, initial, 1000);
177 if (result.loss < best.loss) {
178 best = result;
179 }
180 }
181 return best;
182 }
183
184 solveNarrow(wide) {
185 const A = wide.loss;
186 const c = 2;
187 const A1 = A + 1;
188 const a = [0.25 * A1, 0.25 * A1, A1, 0.25 * A1, 0.2 * A1, 0.2 * A1];
189 return this.spsa(A, a, c, wide.values, 500);
190 }
191
192 spsa(A, a, c, values, iters) {
193 const alpha = 1;
194 const gamma = 0.16666666666666666;
195
196 let best = null;
197 let bestLoss = Infinity;
198 const deltas = new Array(6);
199 const highArgs = new Array(6);
200 const lowArgs = new Array(6);
201
202 for (let k = 0; k < iters; k++) {
203 const ck = c / Math.pow(k + 1, gamma);
204 for (let i = 0; i < 6; i++) {
205 deltas[i] = Math.random() > 0.5 ? 1 : -1;
206 highArgs[i] = values[i] + ck * deltas[i];
207 lowArgs[i] = values[i] - ck * deltas[i];
208 }
209
210 const lossDiff = this.loss(highArgs) - this.loss(lowArgs);
211 for (let i = 0; i < 6; i++) {
212 const g = lossDiff / (2 * ck) * deltas[i];
213 const ak = a[i] / Math.pow(A + k + 1, alpha);
214 values[i] = fix(values[i] - ak * g, i);
215 }
216
217 const loss = this.loss(values);
218 if (loss < bestLoss) {
219 best = values.slice(0);
220 bestLoss = loss;
221 }
222 }
223 return { values: best, loss: bestLoss };
224
225 function fix(value, idx) {
226 let max = 100;
227 if (idx === 2 /* saturate */) {
228 max = 7500;
229 } else if (idx === 4 /* brightness */ || idx === 5 /* contrast */) {
230 max = 200;
231 }
232
233 if (idx === 3 /* hue-rotate */) {
234 if (value > max) {
235 value %= max;
236 } else if (value < 0) {
237 value = max + value % max;
238 }
239 } else if (value < 0) {
240 value = 0;
241 } else if (value > max) {
242 value = max;
243 }
244 return value;
245 }
246 }
247
248 loss(filters) {
249 // Argument is array of percentages.
250 const color = this.reusedColor;
251 color.set(0, 0, 0);
252
253 color.invert(filters[0] / 100);
254 color.sepia(filters[1] / 100);
255 color.saturate(filters[2] / 100);
256 color.hueRotate(filters[3] * 3.6);
257 color.brightness(filters[4] / 100);
258 color.contrast(filters[5] / 100);
259
260 const colorHSL = color.hsl();
261 return (
262 Math.abs(color.r - this.target.r) +
263 Math.abs(color.g - this.target.g) +
264 Math.abs(color.b - this.target.b) +
265 Math.abs(colorHSL.h - this.targetHSL.h) +
266 Math.abs(colorHSL.s - this.targetHSL.s) +
267 Math.abs(colorHSL.l - this.targetHSL.l)
268 );
269 }
270
271 css(filters) {
272 function fmt(idx, multiplier = 1) {
273 return Math.round(filters[idx] * multiplier);
274 }
275 return `filter: invert(${fmt(0)}%) sepia(${fmt(1)}%) saturate(${fmt(2)}%) hue-rotate(${fmt(3, 3.6)}deg) brightness(${fmt(4)}%) contrast(${fmt(5)}%);`;
276 }
277}
278
279function hexToRgb(hex) {
280 // Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
281 const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
282 hex = hex.replace(shorthandRegex, (m, r, g, b) => {
283 return r + r + g + g + b + b;
284 });
285
286 const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
287 return result
288 ? [
289 parseInt(result[1], 16),
290 parseInt(result[2], 16),
291 parseInt(result[3], 16),
292 ]
293 : null;
294}
295
296$(document).ready(() => {
297 $('button.execute').click(() => {
298 const rgb = hexToRgb($('input.target').val());
299 if (rgb.length !== 3) {
300 alert('Invalid format!');
301 return;
302 }
303
304 const color = new Color(rgb[0], rgb[1], rgb[2]);
305 const solver = new Solver(color);
306 const result = solver.solve();
307
308 let lossMsg;
309 if (result.loss < 1) {
310 lossMsg = 'This is a perfect result.';
311 } else if (result.loss < 5) {
312 lossMsg = 'The is close enough.';
313 } else if (result.loss < 15) {
314 lossMsg = 'The color is somewhat off. Consider running it again.';
315 } else {
316 lossMsg = 'The color is extremely off. Run it again!';
317 }
318
319 $('.realPixel').css('background-color', color.toString());
320 $('.filterPixel').attr('style', result.filter);
321 $('.filterDetail').text(result.filter);
322 $('.lossDetail').html(`Loss: ${result.loss.toFixed(1)}. <b>${lossMsg}</b>`);
323 });
324});
325