Farbe 1
diff --git a/js/schema-modal.js b/js/schema-modal.js
index 3b438b8..288523a 100644
--- a/js/schema-modal.js
+++ b/js/schema-modal.js
@@ -1,7 +1,85 @@
import { hexToHsl, hslToHex, rgbToHsl, rgbToHex } from './converter.js';
const MAX_FARBEN = 4;
-const THUMB_SIZE = 120; // max px für Vorschaubild
+const THUMB_SIZE = 120;
+const DISTINCT_THRESHOLD = 45; // Mindest-RGB-Abstand damit zwei Farben als verschieden gelten
+
+// --- K-Means Farbextraktion ---
+
+function colorDist(a, b) {
+ return Math.sqrt((a[0]-b[0])**2 + (a[1]-b[1])**2 + (a[2]-b[2])**2);
+}
+
+function samplePixels(dataUrl, maxSamples, callback) {
+ const img = new Image();
+ img.onload = () => {
+ const c = document.createElement('canvas');
+ c.width = img.width; c.height = img.height;
+ const ctx = c.getContext('2d');
+ ctx.drawImage(img, 0, 0);
+ const data = ctx.getImageData(0, 0, c.width, c.height).data;
+ const total = c.width * c.height;
+ const step = Math.max(1, Math.floor(total / maxSamples));
+ const pixels = [];
+ for (let i = 0; i < total; i += step) {
+ const idx = i * 4;
+ if (data[idx + 3] < 128) continue;
+ pixels.push([data[idx], data[idx+1], data[idx+2]]);
+ }
+ callback(pixels);
+ };
+ img.src = dataUrl;
+}
+
+function kMeans(pixels, k, iterations = 15) {
+ if (pixels.length <= k) return pixels.map(p => [...p]);
+ // K-Means++ Initialisierung: erste zufällig, dann maximaler Mindestabstand
+ const centroids = [pixels[Math.floor(Math.random() * pixels.length)].slice()];
+ for (let c = 1; c < k; c++) {
+ let maxD = -1, best = centroids[0];
+ for (const px of pixels) {
+ const d = Math.min(...centroids.map(ce => colorDist(px, ce)));
+ if (d > maxD) { maxD = d; best = px; }
+ }
+ centroids.push(best.slice());
+ }
+ for (let iter = 0; iter < iterations; iter++) {
+ const clusters = Array.from({length: k}, () => []);
+ for (const px of pixels) {
+ let minD = Infinity, minI = 0;
+ for (let i = 0; i < k; i++) {
+ const d = colorDist(px, centroids[i]);
+ if (d < minD) { minD = d; minI = i; }
+ }
+ clusters[minI].push(px);
+ }
+ for (let i = 0; i < k; i++) {
+ if (clusters[i].length === 0) continue;
+ centroids[i] = [0, 1, 2].map(ch =>
+ Math.round(clusters[i].reduce((s, p) => s + p[ch], 0) / clusters[i].length)
+ );
+ }
+ }
+ return centroids;
+}
+
+function extractDominantColors(dataUrl, callback) {
+ samplePixels(dataUrl, 600, (pixels) => {
+ if (pixels.length === 0) { callback([]); return; }
+ const raw = kMeans(pixels, Math.min(3, pixels.length), 15);
+ // Nur hinreichend verschiedene Farben behalten
+ const distinct = [raw[0]];
+ for (let i = 1; i < raw.length; i++) {
+ if (distinct.every(d => colorDist(raw[i], d) >= DISTINCT_THRESHOLD)) {
+ distinct.push(raw[i]);
+ }
+ }
+ callback(distinct.map(rgb => ({
+ hex: rgbToHex({ r: rgb[0], g: rgb[1], b: rgb[2] }),
+ hsl: rgbToHsl({ r: rgb[0], g: rgb[1], b: rgb[2] })
+ })));
+ });
+}
// Aktuell gesammelter Zustand des Modals
let farben = []; // Array von HSL-Objekten
@@ -39,9 +117,9 @@ function resetModal() {
document.getElementById('schema-farben-preview').textContent = '';
document.getElementById('schema-canvas').style.display = 'none';
document.getElementById('schema-canvas').getContext('2d').clearRect(0, 0, 1, 1);
- document.getElementById('schema-farbe-eingabe').style.display = 'block';
+ document.getElementById('schema-vorschlag').style.display = 'none';
+ document.getElementById('schema-farbe-eingabe').style.display = 'none';
document.getElementById('schema-abschliessen-btn').disabled = true;
- // Vorschaubild-UI zurücksetzen
const preview = document.getElementById('schema-bild-preview');
preview.style.backgroundImage = '';
preview.classList.remove('hat-bild');
@@ -50,6 +128,26 @@ function resetModal() {
updateFarbeLabel();
}
+function showVorschlag(farbenVorschlag) {
+ const container = document.getElementById('schema-vorschlag-farben');
+ container.textContent = '';
+ farbenVorschlag.forEach(({ hex }) => {
+ const cell = document.createElement('div');
+ cell.style.cssText = 'display:inline-flex;flex-direction:column;align-items:center;gap:0.2rem';
+ const swatch = document.createElement('div');
+ swatch.style.cssText = 'width:44px;height:44px;border-radius:6px;border:1px solid #ddd;background:' + hex;
+ const label = document.createElement('span');
+ label.style.cssText = 'font-size:0.7rem;font-family:monospace;color:#666';
+ label.textContent = hex;
+ cell.appendChild(swatch);
+ cell.appendChild(label);
+ container.appendChild(cell);
+ });
+ document.getElementById('schema-vorschlag').style.display = 'block';
+ document.getElementById('schema-farbe-eingabe').style.display = 'none';
+ return farbenVorschlag;
+}
+
function updateFarbeLabel() {
document.getElementById('schema-farbe-label').textContent =
'Farbe ' + (farben.length + 1) + (farben.length === MAX_FARBEN - 1 ? ' (letzte)' : '');
@@ -225,6 +323,8 @@ export function initSchemaModal(onSave) {
document.getElementById('schema-bild-input').click();
});
+ let letzterVorschlag = [];
+
document.getElementById('schema-bild-input').addEventListener('change', async (e) => {
const file = e.target.files[0];
if (!file) return;
@@ -233,14 +333,50 @@ export function initSchemaModal(onSave) {
preview.style.backgroundImage = 'url(' + vorschaubild + ')';
preview.classList.add('hat-bild');
document.getElementById('schema-bild-entfernen-btn').style.display = 'inline-block';
+
+ // Farben extrahieren und Vorschlag zeigen
+ extractDominantColors(vorschaubild, (vorschlag) => {
+ if (vorschlag.length > 0) {
+ letzterVorschlag = showVorschlag(vorschlag);
+ } else {
+ // Keine Farben erkannt → direkt manuell
+ document.getElementById('schema-farbe-eingabe').style.display = 'block';
+ }
+ });
+ });
+
+ // Vorschlag annehmen
+ document.getElementById('schema-vorschlag-annehmen-btn').addEventListener('click', () => {
+ farben = letzterVorschlag.map(v => v.hsl);
+ renderFarbenPreview();
+ document.getElementById('schema-abschliessen-btn').disabled = false;
+ document.getElementById('schema-vorschlag').style.display = 'none';
+ if (farben.length < MAX_FARBEN) {
+ updateFarbeLabel();
+ document.getElementById('schema-farbe-eingabe').style.display = 'block';
+ document.getElementById('schema-hex-input').focus();
+ }
+ });
+
+ // Vorschlag ablehnen → manuell
+ document.getElementById('schema-vorschlag-ablehnen-btn').addEventListener('click', () => {
+ letzterVorschlag = [];
+ document.getElementById('schema-vorschlag').style.display = 'none';
+ document.getElementById('schema-farbe-eingabe').style.display = 'block';
+ document.getElementById('schema-hex-input').focus();
});
document.getElementById('schema-bild-entfernen-btn').addEventListener('click', () => {
vorschaubild = null;
+ letzterVorschlag = [];
const preview = document.getElementById('schema-bild-preview');
preview.style.backgroundImage = '';
preview.classList.remove('hat-bild');
document.getElementById('schema-bild-input').value = '';
document.getElementById('schema-bild-entfernen-btn').style.display = 'none';
+ document.getElementById('schema-vorschlag').style.display = 'none';
+ document.getElementById('schema-farbe-eingabe').style.display = 'block';
+ // Farben zurücksetzen falls nur vom Vorschlag
+ if (farben.length === 0) updateFarbeLabel();
});
}