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();