init
This commit is contained in:
commit
adc618f153
31 changed files with 769 additions and 0 deletions
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
*.png filter=lfs diff=lfs merge=lfs -text
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
/target
|
||||||
|
*.blend1
|
||||||
|
*.blend2
|
||||||
|
*.blend3
|
4
.vscode/settings.json
vendored
Normal file
4
.vscode/settings.json
vendored
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
{
|
||||||
|
"deno.enable": true,
|
||||||
|
"deno.unstable": true
|
||||||
|
}
|
75
gen.ts
Executable file
75
gen.ts
Executable file
|
@ -0,0 +1,75 @@
|
||||||
|
#!/bin/env -S deno run -A
|
||||||
|
|
||||||
|
const promises = [] as Promise<unknown>[];
|
||||||
|
const target = Deno.cwd() + "/target";
|
||||||
|
const in_flight = new Set<string>();
|
||||||
|
|
||||||
|
function wait(ms: number): Promise<void> {
|
||||||
|
return new Promise((res) => setTimeout(() => res(), ms));
|
||||||
|
}
|
||||||
|
async function block_while(l: () => boolean) {
|
||||||
|
while (l()) await wait(100);
|
||||||
|
}
|
||||||
|
|
||||||
|
await Deno.mkdir("target", { recursive: true });
|
||||||
|
await Deno.remove("target", { recursive: true });
|
||||||
|
await Deno.mkdir("target");
|
||||||
|
await Deno.mkdir("target/pdf");
|
||||||
|
await Deno.mkdir("target/png");
|
||||||
|
|
||||||
|
for (const set of ["club", "diamond", "heart", "spade"]) {
|
||||||
|
for (let i = 1; i <= 14; i += 1) {
|
||||||
|
const id = `${set}-${i}`;
|
||||||
|
await block_while(() => (in_flight.size >= 8));
|
||||||
|
promises.push(
|
||||||
|
(async () => {
|
||||||
|
const tmp_path = `/tmp/models-${
|
||||||
|
Date.now() + Math.floor(Math.random() * 100000)
|
||||||
|
}`;
|
||||||
|
await Deno.mkdir(tmp_path + "/base", { recursive: true });
|
||||||
|
const [html, pdf, png] = [
|
||||||
|
`${tmp_path}/base/page.html`,
|
||||||
|
`${target}/pdf/${id}.pdf`,
|
||||||
|
`${target}/png/${id}.png`,
|
||||||
|
];
|
||||||
|
await Deno.writeTextFile(
|
||||||
|
html,
|
||||||
|
Deno.readTextFileSync("./models/base/card.html").split("\n").map((
|
||||||
|
l,
|
||||||
|
) =>
|
||||||
|
l.startsWith(" <script> const card = ")
|
||||||
|
? `<script> const card = { "num": ${i}, "suite": '${set}' }; </script>`
|
||||||
|
: l
|
||||||
|
).join("\n"),
|
||||||
|
);
|
||||||
|
const script = `
|
||||||
|
cp -r ./models/* "${tmp_path}"
|
||||||
|
cd "${tmp_path}/base"
|
||||||
|
chromium --headless --disable-gpu --run-all-compositor-stages-before-draw --no-pdf-header-footer --print-to-pdf="${pdf}" "${html}"
|
||||||
|
`;
|
||||||
|
await Deno.run({ cmd: ["sh", "-c", script] }).status();
|
||||||
|
await Deno.run({
|
||||||
|
cmd: [
|
||||||
|
"convert",
|
||||||
|
["-density", "900"],
|
||||||
|
[pdf],
|
||||||
|
["-quality", "100"],
|
||||||
|
png,
|
||||||
|
].flat(),
|
||||||
|
})
|
||||||
|
.status();
|
||||||
|
console.log(`[gen.ts] completed '${id}'`);
|
||||||
|
in_flight.delete(id);
|
||||||
|
await Deno.remove(tmp_path, { recursive: true });
|
||||||
|
})(),
|
||||||
|
);
|
||||||
|
in_flight.add(id);
|
||||||
|
console.log(`[gen.ts] launched '${id}'`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
|
console.log("[gen.ts] done");
|
||||||
|
if (in_flight.size > 0) {
|
||||||
|
console.log(`[gen.ts] failed '${Array.from(in_flight.keys()).join()}'`);
|
||||||
|
}
|
72
models/base/card.html
Normal file
72
models/base/card.html
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<link rel="stylesheet" href="../card-format.css">
|
||||||
|
<title>Card</title>
|
||||||
|
<script src="../colorize.js"></script>
|
||||||
|
<script> const card = { "num": 1, "suite": 'heart' }; </script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="card">
|
||||||
|
<div id="top">
|
||||||
|
<div style="margin: 1rem; margin-left: 0.6rem; display: grid;">
|
||||||
|
<div style="display: grid; width: 1.8rem; grid-template-columns: auto; place-items: center;">
|
||||||
|
<h1 style="margin: 0;"></h1>
|
||||||
|
<img id="suite-icon" style="width: 20px;">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<style>
|
||||||
|
img {
|
||||||
|
filter: var(--color-mask-filters);
|
||||||
|
}
|
||||||
|
|
||||||
|
#suite {
|
||||||
|
position: fixed;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
|
||||||
|
width: 70mm;
|
||||||
|
height: 121mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
#suite img {
|
||||||
|
margin: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suite-column {
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
margin: -0.5rem;
|
||||||
|
rotate: 0.5turn;
|
||||||
|
}
|
||||||
|
|
||||||
|
#suite img:nth-child(2n) {
|
||||||
|
rotate: 0.5turn;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suite-column:nth-child(2) {
|
||||||
|
rotate: 0turn;
|
||||||
|
}
|
||||||
|
|
||||||
|
.suite-column:nth-child(2) img:nth-child(2n) {
|
||||||
|
rotate: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div id="suite">
|
||||||
|
<img style="width: 40px;">
|
||||||
|
<div style="display: grid; grid-template-columns: auto auto auto; place-items: center;">
|
||||||
|
<div class="suite-column"></div>
|
||||||
|
<div class="suite-column"></div>
|
||||||
|
<div class="suite-column"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script src="../suite.js"></script>
|
||||||
|
</div>
|
||||||
|
<script src="../reverse.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
86
models/card-format.css
Normal file
86
models/card-format.css
Normal file
|
@ -0,0 +1,86 @@
|
||||||
|
:root {
|
||||||
|
--bg: #181818;
|
||||||
|
--color: #ffc82f;
|
||||||
|
--color-mask-filters: ;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
margin: 0;
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#card {
|
||||||
|
zoom: 124.988%;
|
||||||
|
width: 70mm;
|
||||||
|
height: 121mm;
|
||||||
|
margin: 1cm auto;
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
@page {
|
||||||
|
size: 70mm 121mm;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media print {
|
||||||
|
@page {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
image-resolution: 900dpi;
|
||||||
|
}
|
||||||
|
|
||||||
|
html,
|
||||||
|
body {
|
||||||
|
background-color: var(--bg);
|
||||||
|
zoom: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#card {
|
||||||
|
margin: 0;
|
||||||
|
border: initial;
|
||||||
|
border-radius: initial;
|
||||||
|
box-shadow: initial;
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: Font1;
|
||||||
|
src: url("fonts/Cranberry\ Gin.otf");
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--bg: #181818;
|
||||||
|
--color: #ffc82f;
|
||||||
|
--color-mask-filters: ;
|
||||||
|
}
|
||||||
|
|
||||||
|
#card {
|
||||||
|
background-color: var(--bg);
|
||||||
|
color: var(--color);
|
||||||
|
font-family: Font1;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
#top {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
#bottom {
|
||||||
|
position: absolute;
|
||||||
|
transform-origin: 35mm 60.5mm;
|
||||||
|
transform: rotate(0.5turn);
|
||||||
|
}
|
325
models/colorize.js
Normal file
325
models/colorize.js
Normal file
|
@ -0,0 +1,325 @@
|
||||||
|
class Color {
|
||||||
|
constructor(r, g, b) {
|
||||||
|
this.set(r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
toString() {
|
||||||
|
return `rgb(${Math.round(this.r)}, ${Math.round(this.g)}, ${Math.round(this.b)})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
set(r, g, b) {
|
||||||
|
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.140,
|
||||||
|
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) {
|
||||||
|
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() {
|
||||||
|
// 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, s, 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) {
|
||||||
|
if (value > 255) {
|
||||||
|
value = 255;
|
||||||
|
} else if (value < 0) {
|
||||||
|
value = 0;
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Solver {
|
||||||
|
constructor(target, baseColor) {
|
||||||
|
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() {
|
||||||
|
const A = 5;
|
||||||
|
const c = 15;
|
||||||
|
const a = [60, 180, 18000, 600, 1.2, 1.2];
|
||||||
|
|
||||||
|
let best = {
|
||||||
|
loss: Infinity
|
||||||
|
};
|
||||||
|
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) {
|
||||||
|
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, a, c, values, iters) {
|
||||||
|
const alpha = 1;
|
||||||
|
const gamma = 0.16666666666666666;
|
||||||
|
|
||||||
|
let best = null;
|
||||||
|
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, idx) {
|
||||||
|
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) {
|
||||||
|
// 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) {
|
||||||
|
function fmt(idx, multiplier = 1) {
|
||||||
|
return Math.round(filters[idx] * multiplier);
|
||||||
|
}
|
||||||
|
return `filter: invert(${fmt(0)}%) sepia(${fmt(1)}%) saturate(${fmt(2)}%) hue-rotate(${fmt(3, 3.6)}deg) brightness(${fmt(4)}%) contrast(${fmt(5)}%);`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hexToRgb(hex) {
|
||||||
|
// 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);
|
||||||
|
return result ?
|
||||||
|
[
|
||||||
|
parseInt(result[1], 16),
|
||||||
|
parseInt(result[2], 16),
|
||||||
|
parseInt(result[3], 16),
|
||||||
|
] :
|
||||||
|
null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function colorize(incColor, mode) {
|
||||||
|
incColor = incColor || '#000000';
|
||||||
|
if (incColor.includes('rgb')) {
|
||||||
|
incColor = incColor.toLowerCase().replace('rgb(', '').replace(')', '').split(',')
|
||||||
|
} else if (incColor.includes('#')) {
|
||||||
|
if (incColor.length == 5) {
|
||||||
|
incColor = incColor.slice(0, incColor - 1)
|
||||||
|
}
|
||||||
|
if (incColor.length > 7) {
|
||||||
|
incColor = incColor.slice(0, incColor - 2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const rgb = !Array.isArray(incColor) && (mode == 'hex' || !incColor.includes('rgb')) ? hexToRgb(incColor) : incColor;
|
||||||
|
|
||||||
|
if (rgb.length !== 3) {
|
||||||
|
return 'invalid input';
|
||||||
|
}
|
||||||
|
const color = new Color(rgb[0], rgb[1], rgb[2]);
|
||||||
|
const solver = new Solver(color);
|
||||||
|
const result = solver.solve();
|
||||||
|
return result;
|
||||||
|
} catch (e) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
BIN
models/family.blend
Normal file
BIN
models/family.blend
Normal file
Binary file not shown.
BIN
models/fonts/Cranberry Gin.otf
Normal file
BIN
models/fonts/Cranberry Gin.otf
Normal file
Binary file not shown.
BIN
models/images/C.png
(Stored with Git LFS)
Normal file
BIN
models/images/C.png
(Stored with Git LFS)
Normal file
Binary file not shown.
9
models/images/C.svg
Normal file
9
models/images/C.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 14 KiB |
BIN
models/images/C_.png
(Stored with Git LFS)
Normal file
BIN
models/images/C_.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
models/images/D.png
(Stored with Git LFS)
Normal file
BIN
models/images/D.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
models/images/R.png
(Stored with Git LFS)
Normal file
BIN
models/images/R.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
models/images/V.png
(Stored with Git LFS)
Normal file
BIN
models/images/V.png
(Stored with Git LFS)
Normal file
Binary file not shown.
56
models/images/VDR.svg
Normal file
56
models/images/VDR.svg
Normal file
File diff suppressed because one or more lines are too long
After Width: | Height: | Size: 46 KiB |
BIN
models/images/VDR_.png
(Stored with Git LFS)
Normal file
BIN
models/images/VDR_.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
models/images/club.png
(Stored with Git LFS)
Normal file
BIN
models/images/club.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
models/images/club2.png
(Stored with Git LFS)
Normal file
BIN
models/images/club2.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
models/images/diamond.png
(Stored with Git LFS)
Normal file
BIN
models/images/diamond.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
models/images/diamond2.png
(Stored with Git LFS)
Normal file
BIN
models/images/diamond2.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
models/images/heart.png
(Stored with Git LFS)
Normal file
BIN
models/images/heart.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
models/images/heart2.png
(Stored with Git LFS)
Normal file
BIN
models/images/heart2.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
models/images/spade.png
(Stored with Git LFS)
Normal file
BIN
models/images/spade.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
models/images/spade2.png
(Stored with Git LFS)
Normal file
BIN
models/images/spade2.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
models/images/spade3.png
(Stored with Git LFS)
Normal file
BIN
models/images/spade3.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
models/images/spade4.png
(Stored with Git LFS)
Normal file
BIN
models/images/spade4.png
(Stored with Git LFS)
Normal file
Binary file not shown.
BIN
models/images/spade5.png
(Stored with Git LFS)
Normal file
BIN
models/images/spade5.png
(Stored with Git LFS)
Normal file
Binary file not shown.
4
models/reverse.js
Normal file
4
models/reverse.js
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
const top_element = document.getElementById("top");
|
||||||
|
const copy = top_element.cloneNode(true);
|
||||||
|
copy.id = "bottom";
|
||||||
|
top_element.parentNode.appendChild(copy);
|
82
models/suite.js
Normal file
82
models/suite.js
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
/** @type {number} */
|
||||||
|
const num = card.num;
|
||||||
|
/** @type {string} */
|
||||||
|
const suite = card.suite;
|
||||||
|
|
||||||
|
const images = {
|
||||||
|
'spade': [
|
||||||
|
'../images/spade3.png',
|
||||||
|
'../images/spade5.png',
|
||||||
|
],
|
||||||
|
'diamond': [
|
||||||
|
'../images/diamond.png',
|
||||||
|
'../images/diamond2.png'
|
||||||
|
],
|
||||||
|
'club': [
|
||||||
|
'../images/club.png',
|
||||||
|
'../images/club2.png'
|
||||||
|
],
|
||||||
|
'heart': [
|
||||||
|
'../images/heart.png',
|
||||||
|
'../images/heart2.png'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
const colors = {
|
||||||
|
'spade': '#ffffff',
|
||||||
|
'club': '#ffffff',
|
||||||
|
'diamond': '#ffc82f',
|
||||||
|
'heart': '#ffc82f',
|
||||||
|
}
|
||||||
|
let { filter } = colorize(colors[suite]);
|
||||||
|
filter = "invert(100%) " + filter.substring(8);
|
||||||
|
filter = filter.substring(0, filter.length - 1);
|
||||||
|
console.log(filter);
|
||||||
|
|
||||||
|
document.documentElement.style.setProperty("--color", colors[suite]);
|
||||||
|
document.documentElement.style.setProperty("--color-mask-filters", filter);
|
||||||
|
|
||||||
|
function card_name(num) {
|
||||||
|
if (num <= 10) return `${num}`;
|
||||||
|
return ['V', 'C', 'D', 'R'][num - 11];
|
||||||
|
}
|
||||||
|
|
||||||
|
const name_ = card_name(num);
|
||||||
|
document.querySelector("h1").innerText = name_;
|
||||||
|
|
||||||
|
const img = document.getElementById('suite-icon').src = images[suite][0];
|
||||||
|
|
||||||
|
/** @type {HTMLImageElement} */
|
||||||
|
const elem = document.querySelector("#suite img");
|
||||||
|
elem.src = images[suite][1];
|
||||||
|
elem.parentElement.removeChild(elem);
|
||||||
|
|
||||||
|
const exceptions = [
|
||||||
|
['V', '../images/V.png'],
|
||||||
|
['C', '../images/C.png'],
|
||||||
|
['D', '../images/D.png'],
|
||||||
|
['R', '../images/R.png'],
|
||||||
|
];
|
||||||
|
for (const [n, path] of exceptions)
|
||||||
|
if (name_ === n) { elem.src = path; elem.style.width = 250 + "px" }
|
||||||
|
|
||||||
|
const grid = document.querySelector("#suite div");
|
||||||
|
let i = num;
|
||||||
|
if (i > 10) i = 1;
|
||||||
|
while (i > 0) {
|
||||||
|
if (i >= 3) {
|
||||||
|
grid.children.item(0).append(elem.cloneNode(true));
|
||||||
|
grid.children.item(1).append(elem.cloneNode(true));
|
||||||
|
grid.children.item(2).append(elem.cloneNode(true));
|
||||||
|
i -= 3;
|
||||||
|
}
|
||||||
|
if (i >= 2) {
|
||||||
|
grid.children.item(0).append(elem.cloneNode(true));
|
||||||
|
grid.children.item(2).append(elem.cloneNode(true));
|
||||||
|
i -= 2;
|
||||||
|
}
|
||||||
|
if (i === 1) {
|
||||||
|
grid.children.item(1).append(elem.cloneNode(true));
|
||||||
|
i -= 1;
|
||||||
|
}
|
||||||
|
}
|
BIN
models/suites.blend
Normal file
BIN
models/suites.blend
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue