Spaces:
Running
Running
File size: 3,741 Bytes
beaca28 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | const softAutumn = [
{ name: "Moss", hex: "#7A8450" },
{ name: "Olive", hex: "#6F6B3C" },
{ name: "Sage", hex: "#8A9273" },
{ name: "Camel", hex: "#B08A63" },
{ name: "Terracotta", hex: "#B66545" },
{ name: "Rust", hex: "#9A4F2F" },
{ name: "Peach", hex: "#D7A47E" },
{ name: "Dusty Coral", hex: "#C9826D" },
{ name: "Warm Taupe", hex: "#8E7360" },
{ name: "Mushroom", hex: "#A38B78" },
{ name: "Warm Navy", hex: "#3E4D58" },
{ name: "Soft Teal", hex: "#5F7E78" }
];
const video = document.getElementById("video");
const startBtn = document.getElementById("startBtn");
const scanBtn = document.getElementById("scanBtn");
const statusEl = document.getElementById("status");
const sampleSwatch = document.getElementById("sampleSwatch");
const sampleHexEl = document.getElementById("sampleHex");
const paletteGrid = document.getElementById("paletteGrid");
const captureCanvas = document.getElementById("captureCanvas");
const ctx = captureCanvas.getContext("2d", { willReadFrequently: true });
let stream;
function hexToRgb(hex) {
const clean = hex.replace("#", "");
return {
r: parseInt(clean.slice(0, 2), 16),
g: parseInt(clean.slice(2, 4), 16),
b: parseInt(clean.slice(4, 6), 16)
};
}
function rgbToHex({ r, g, b }) {
return (
"#" +
[r, g, b]
.map((value) => value.toString(16).padStart(2, "0"))
.join("")
.toUpperCase()
);
}
function colorDistance(a, b) {
return Math.sqrt(
2 * (a.r - b.r) ** 2 +
4 * (a.g - b.g) ** 2 +
3 * (a.b - b.b) ** 2
);
}
function averageCenterColor() {
const w = captureCanvas.width;
const h = captureCanvas.height;
ctx.drawImage(video, 0, 0, w, h);
const data = ctx.getImageData(0, 0, w, h).data;
let r = 0;
let g = 0;
let b = 0;
let n = 0;
for (let y = 10; y < 30; y++) {
for (let x = 10; x < 30; x++) {
const i = (y * w + x) * 4;
r += data[i];
g += data[i + 1];
b += data[i + 2];
n += 1;
}
}
return {
r: Math.round(r / n),
g: Math.round(g / n),
b: Math.round(b / n)
};
}
function renderPalette() {
paletteGrid.innerHTML = "";
for (const color of softAutumn) {
const chip = document.createElement("div");
chip.className = "chip";
chip.innerHTML = `<div class="chip-color" style="background:${color.hex}"></div>${color.name}`;
paletteGrid.appendChild(chip);
}
}
startBtn.addEventListener("click", async () => {
try {
stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: { ideal: "environment" } },
audio: false
});
video.srcObject = stream;
scanBtn.disabled = false;
statusEl.textContent = "Status: camera ready.";
} catch (error) {
statusEl.textContent = `Status: camera error: ${error.message}`;
}
});
scanBtn.addEventListener("click", () => {
if (!video.videoWidth) {
statusEl.textContent = "Status: camera is not ready yet.";
return;
}
const sample = averageCenterColor();
const sampleHex = rgbToHex(sample);
let closest;
for (const paletteColor of softAutumn) {
const distance = colorDistance(sample, hexToRgb(paletteColor.hex));
if (!closest || distance < closest.distance) {
closest = { ...paletteColor, distance };
}
}
const threshold = 85;
const isMatch = closest.distance <= threshold;
sampleSwatch.style.background = sampleHex;
sampleHexEl.textContent = sampleHex;
statusEl.innerHTML = isMatch
? `<span class="ok">Status: Match YES.</span> Closest: ${closest.name} ${closest.hex} (distance ${closest.distance.toFixed(1)})`
: `<span class="bad">Status: Match NO.</span> Closest: ${closest.name} ${closest.hex} (distance ${closest.distance.toFixed(1)})`;
});
renderPalette();
|