MediaWiki:Gadget-LabelScan.js: Unterschied zwischen den Versionen
Erscheinungsbild
Admin (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
Admin (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
||
| Zeile 115: | Zeile 115: | ||
document.addEventListener('DOMContentLoaded', bindUI); | document.addEventListener('DOMContentLoaded', bindUI); | ||
} else bindUI(); | } else bindUI(); | ||
})(); | |||
// --- kleine UI-Helfer für LabelScan-Seite --- | |||
(function(){ | |||
const wrap = document.getElementById('ados-labelscan'); | |||
if (!wrap) return; | |||
const fileIn = document.getElementById('ados-scan-file'); | |||
const bigBtn = document.getElementById('ados-scan-bigbtn'); | |||
const drop = document.getElementById('ados-scan-drop'); | |||
const preview = document.getElementById('ados-scan-preview'); | |||
// großer Button triggert File-Input | |||
if (bigBtn && fileIn) { | |||
bigBtn.addEventListener('click', () => fileIn.click()); | |||
} | |||
// Bildvorschau | |||
if (fileIn && preview) { | |||
fileIn.addEventListener('change', () => { | |||
if (fileIn.files && fileIn.files[0]) { | |||
const url = URL.createObjectURL(fileIn.files[0]); | |||
preview.innerHTML = `<img src="${url}" alt="Vorschau">`; | |||
} | |||
}); | |||
} | |||
// Drag&Drop | |||
if (drop && fileIn) { | |||
['dragenter','dragover'].forEach(ev => drop.addEventListener(ev, e => { | |||
e.preventDefault(); e.stopPropagation(); drop.classList.add('is-dragover'); | |||
})); | |||
['dragleave','drop'].forEach(ev => drop.addEventListener(ev, e => { | |||
e.preventDefault(); e.stopPropagation(); drop.classList.remove('is-dragover'); | |||
})); | |||
drop.addEventListener('drop', e => { | |||
const dt = e.dataTransfer; | |||
if (dt && dt.files && dt.files[0]) { | |||
fileIn.files = dt.files; | |||
const url = URL.createObjectURL(dt.files[0]); | |||
preview.innerHTML = `<img src="${url}" alt="Vorschau">`; | |||
} | |||
}); | |||
} | |||
})(); | })(); | ||
Version vom 7. November 2025, 21:28 Uhr
/* global mw */
(function () {
'use strict';
// -------------------------------------------------------
// 1) INDEX LADEN
// -------------------------------------------------------
const INDEX_URL = '/wiki?title=MediaWiki:Gadget-LabelScan-index.json&action=raw&ctype=application/json';
let LABEL_INDEX = null;
async function loadIndex() {
if (LABEL_INDEX) return LABEL_INDEX;
const res = await fetch(INDEX_URL);
LABEL_INDEX = await res.json();
console.log('[LabelScan] Index geladen:', LABEL_INDEX.length, 'Einträge');
return LABEL_INDEX;
}
// -------------------------------------------------------
// 2) KOSINUS-SIMILARITÄT (Bild→Vektor→Vergleich)
// -> ultra leichtgewichtige Bildähnlichkeit
// -------------------------------------------------------
async function imgToVec(fileOrUrl) {
return new Promise((resolve, reject) => {
const img = new Image();
img.crossOrigin = 'anonymous';
img.onload = () => {
const c = document.createElement('canvas');
c.width = 16; c.height = 16;
const ctx = c.getContext('2d');
ctx.drawImage(img, 0, 0, 16, 16);
const d = ctx.getImageData(0, 0, 16, 16).data;
const vec = [];
for (let i = 0; i < d.length; i += 4) vec.push((d[i] + d[i+1] + d[i+2]) / 3);
resolve(vec);
};
img.onerror = reject;
img.src = typeof fileOrUrl === 'string' ? fileOrUrl : URL.createObjectURL(fileOrUrl);
});
}
function cosine(a, b) {
let dot = 0, na = 0, nb = 0;
for (let i = 0; i < a.length; i++) {
dot += a[i] * b[i];
na += a[i] * a[i];
nb += b[i] * b[i];
}
return dot / (Math.sqrt(na) * Math.sqrt(nb));
}
// -------------------------------------------------------
// 3) SUCHE BESTES MATCH
// -------------------------------------------------------
async function findMatch(file) {
await loadIndex();
const vec = await imgToVec(file);
let best = null;
for (const entry of LABEL_INDEX) {
if (!entry.img) continue;
const v = await imgToVec(entry.img);
const score = cosine(vec, v);
if (!best || score > best.score) best = { score, entry };
}
if (!best || best.score < 0.82) return null; // Schwelle fein justierbar
return best.entry;
}
// -------------------------------------------------------
// 4) UI
// -------------------------------------------------------
function hasUI() {
return document.getElementById('ados-scan-run');
}
function bindUI() {
if (!hasUI()) return;
const fileIn = document.getElementById('ados-scan-file');
const runBtn = document.getElementById('ados-scan-run');
const results = document.getElementById('ados-scan-results');
runBtn.addEventListener('click', async function (ev) {
ev.preventDefault();
if (!(fileIn.files && fileIn.files[0])) {
alert('Bitte ein Bild auswählen.');
return;
}
runBtn.disabled = true;
runBtn.textContent = 'Erkenne…';
const match = await findMatch(fileIn.files[0]);
runBtn.disabled = false;
runBtn.textContent = 'Erkennen & suchen';
if (!match) {
results.innerHTML = '<div style="padding:6px;">Keine Treffer. Bitte anderes Foto versuchen.</div>';
return;
}
const link = mw.util.getUrl(match.title.replace(/ /g, '_'));
results.innerHTML = `
<div class="ados-hit">
<b><a href="${link}">${match.title}</a></b><br/>
<img src="${match.img}" style="max-width:150px; margin-top:6px;">
</div>`;
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', bindUI);
} else bindUI();
})();
// --- kleine UI-Helfer für LabelScan-Seite ---
(function(){
const wrap = document.getElementById('ados-labelscan');
if (!wrap) return;
const fileIn = document.getElementById('ados-scan-file');
const bigBtn = document.getElementById('ados-scan-bigbtn');
const drop = document.getElementById('ados-scan-drop');
const preview = document.getElementById('ados-scan-preview');
// großer Button triggert File-Input
if (bigBtn && fileIn) {
bigBtn.addEventListener('click', () => fileIn.click());
}
// Bildvorschau
if (fileIn && preview) {
fileIn.addEventListener('change', () => {
if (fileIn.files && fileIn.files[0]) {
const url = URL.createObjectURL(fileIn.files[0]);
preview.innerHTML = `<img src="${url}" alt="Vorschau">`;
}
});
}
// Drag&Drop
if (drop && fileIn) {
['dragenter','dragover'].forEach(ev => drop.addEventListener(ev, e => {
e.preventDefault(); e.stopPropagation(); drop.classList.add('is-dragover');
}));
['dragleave','drop'].forEach(ev => drop.addEventListener(ev, e => {
e.preventDefault(); e.stopPropagation(); drop.classList.remove('is-dragover');
}));
drop.addEventListener('drop', e => {
const dt = e.dataTransfer;
if (dt && dt.files && dt.files[0]) {
fileIn.files = dt.files;
const url = URL.createObjectURL(dt.files[0]);
preview.innerHTML = `<img src="${url}" alt="Vorschau">`;
}
});
}
})();