MediaWiki:Gadget-LabelScan.js: Unterschied zwischen den Versionen
Admin (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
Admin (Diskussion | Beiträge) Keine Bearbeitungszusammenfassung |
||
| Zeile 1: | Zeile 1: | ||
/* | /* global mw */ | ||
(function () { | (function () { | ||
'use strict'; | '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) | |||
function | // -> 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]; | |||
return | na += a[i] * a[i]; | ||
nb += b[i] * b[i]; | |||
} | |||
return dot / (Math.sqrt(na) * Math.sqrt(nb)); | |||
} | } | ||
// | // ------------------------------------------------------- | ||
// 3) SUCHE BESTES MATCH | |||
async function | // ------------------------------------------------------- | ||
async function findMatch(file) { | |||
await loadIndex(); | |||
const vec = await imgToVec(file); | |||
await | 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 | return document.getElementById('ados-scan-run'); | ||
} | } | ||
function bindUI() { | |||
if (!hasUI()) return; | |||
const | const fileIn = document.getElementById('ados-scan-file'); | ||
const | 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>`; | |||
const link = mw.util.getUrl( | |||
}); | }); | ||
} | } | ||
if (document.readyState === 'loading') { | |||
document.addEventListener('DOMContentLoaded', bindUI); | |||
} else bindUI(); | |||
})(); | })(); | ||