Zum Inhalt springen

MediaWiki:Gadget-LabelScan.js: Unterschied zwischen den Versionen

Aus ADOS Wiki
Keine Bearbeitungszusammenfassung
Keine Bearbeitungszusammenfassung
Zeile 1: Zeile 1:
/* global mw */
/* global mw */
(function () {
(function () {
   'use strict';
   'use strict';


   // -------------------------------------------------------
   // === Hilfsfunktionen =====================================================
  // 1) INDEX LADEN
   async function fileToImage (file) {
  // -------------------------------------------------------
  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) => {
     return new Promise((resolve, reject) => {
       const img = new Image();
       const img = new Image();
      img.crossOrigin = 'anonymous';
       img.onload = () => resolve(img);
       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.onerror = reject;
       img.src = typeof fileOrUrl === 'string' ? fileOrUrl : URL.createObjectURL(fileOrUrl);
       img.src = URL.createObjectURL(file);
     });
     });
   }
   }


   function cosine(a, b) {
   function getCanvas (img, size = 32) {
     let dot = 0, na = 0, nb = 0;
     const c = document.createElement('canvas');
     for (let i = 0; i < a.length; i++) {
    c.width = c.height = size;
       dot += a[i] * b[i];
    const ctx = c.getContext('2d');
      na += a[i] * a[i];
    ctx.drawImage(img, 0, 0, size, size);
      nb += b[i] * b[i];
    return ctx.getImageData(0, 0, size, size).data;
  }
 
  function computePhash (img) {
    const size = 32;
    const pixels = getCanvas(img, size);
    const gray = [];
     for (let i = 0; i < pixels.length; i += 4) {
       gray.push(0.299 * pixels[i] + 0.587 * pixels[i + 1] + 0.114 * pixels[i + 2]);
     }
     }
     return dot / (Math.sqrt(na) * Math.sqrt(nb));
     const avg = gray.reduce((a, b) => a + b, 0) / gray.length;
    const bits = gray.map(v => (v > avg ? 1 : 0));
    let hash = '';
    for (let i = 0; i < bits.length; i += 4) {
      const nibble = bits.slice(i, i + 4).reduce((a, b, j) => a | (b << (3 - j)), 0);
      hash += nibble.toString(16);
    }
    return hash;
   }
   }


   // -------------------------------------------------------
   function hamming (h1, h2) {
  // 3) SUCHE BESTES MATCH
     let d = 0;
  // -------------------------------------------------------
     const len = Math.min(h1.length, h2.length);
  async function findMatch(file) {
     for (let i = 0; i < len; i++) {
     await loadIndex();
       const x = parseInt(h1[i], 16) ^ parseInt(h2[i], 16);
     const vec = await imgToVec(file);
       d += x.toString(2).replace(/0/g, '').length;
 
     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 };
     }
     }
    return d;
  }


     if (!best || best.score < 0.82) return null; // Schwelle fein justierbar
  async function loadIndex () {
     return best.entry;
     const url = mw.util.getUrl('MediaWiki:Gadget-LabelScan-index.json', { action: 'raw', ctype: 'application/json' });
    const res = await fetch(url);
     return res.json();
   }
   }


   // -------------------------------------------------------
   // === UI-Helfer ===========================================================
  // 4) UI
   function showPreview (file) {
  // -------------------------------------------------------
     const prev = document.getElementById('ados-scan-preview');
   function hasUI() {
    if (prev) prev.innerHTML = `<img alt="Vorschau" src="${URL.createObjectURL(file)}">`;
     return document.getElementById('ados-scan-run');
   }
   }
 
   function setStatus (t) {
   function bindUI() {
     const el = document.getElementById('ados-scan-status');
    if (!hasUI()) return;
     if (el) el.textContent = t;
     const fileIn = document.getElementById('ados-scan-file');
  }
     const runBtn = document.getElementById('ados-scan-run');
  function renderResults (hits) {
     const results = document.getElementById('ados-scan-results');
     const box = document.getElementById('ados-scan-results');
 
     box.innerHTML = '';
     runBtn.addEventListener('click', async function (ev) {
    if (!hits.length) {
      ev.preventDefault();
       box.innerHTML = '<div>Keine klaren Treffer gefunden.</div>';
      if (!(fileIn.files && fileIn.files[0])) {
      return;
        alert('Bitte ein Bild auswählen.');
    }
        return;
    hits.forEach(h => {
      }
       box.innerHTML += `
 
      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">
         <div class="ados-hit">
           <b><a href="${link}">${match.title}</a></b><br/>
           <a href="${mw.util.getUrl(h.title)}">
           <img src="${match.img}" style="max-width:150px; margin-top:6px;">
            <img src="${h.thumb}" alt="${h.title}">
            <br><b>${mw.html.escape(h.title)}</b>
          </a>
           <small>Distanz: ${h.dist}</small>
         </div>`;
         </div>`;
     });
     });
   }
   }


   if (document.readyState === 'loading') {
   // === Haupt-Bindung =======================================================
     document.addEventListener('DOMContentLoaded', bindUI);
  async function bind () {
  } else bindUI();
    const runBtn = document.getElementById('ados-scan-run');
})();
     const fileIn = document.getElementById('ados-scan-file');
 
    const bigBtn = document.getElementById('ados-scan-bigbtn');
 
    if (!runBtn || !fileIn) return;


    bigBtn?.addEventListener('click', () => fileIn.click());
    fileIn.addEventListener('change', e => { if (e.target.files[0]) showPreview(e.target.files[0]); });


    runBtn.addEventListener('click', async ev => {
      ev.preventDefault();
      const file = fileIn.files[0];
      if (!file) return alert('Bitte ein Bild wählen.');
      try {
        setStatus('Berechne Hash …');
        const img = await fileToImage(file);
        const ph = computePhash(img);


        setStatus('Lade Index …');
        const idx = await loadIndex();


        setStatus('Vergleiche …');
        const scored = idx.map(it => ({ ...it, dist: hamming(ph, it.phash) }));
        scored.sort((a, b) => a.dist - b.dist);


 
        const top = scored.slice(0, 5);
// --- kleine UI-Helfer für LabelScan-Seite ---
        renderResults(top);
(function(){
        setStatus('Fertig.');
  const wrap = document.getElementById('ados-labelscan');
      } catch (e) {
  if (!wrap) return;
        console.error('[LabelScan]', e);
 
        setStatus('Fehler bei Erkennung.');
  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 (document.readyState === 'loading') {
   if (drop && fileIn) {
     document.addEventListener('DOMContentLoaded', bind);
    ['dragenter','dragover'].forEach(ev => drop.addEventListener(ev, e => {
  } else {
      e.preventDefault(); e.stopPropagation(); drop.classList.add('is-dragover');
     bind();
    }));
    ['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:52 Uhr

/* global mw */
(function () {
  'use strict';

  // === Hilfsfunktionen =====================================================
  async function fileToImage (file) {
    return new Promise((resolve, reject) => {
      const img = new Image();
      img.onload = () => resolve(img);
      img.onerror = reject;
      img.src = URL.createObjectURL(file);
    });
  }

  function getCanvas (img, size = 32) {
    const c = document.createElement('canvas');
    c.width = c.height = size;
    const ctx = c.getContext('2d');
    ctx.drawImage(img, 0, 0, size, size);
    return ctx.getImageData(0, 0, size, size).data;
  }

  function computePhash (img) {
    const size = 32;
    const pixels = getCanvas(img, size);
    const gray = [];
    for (let i = 0; i < pixels.length; i += 4) {
      gray.push(0.299 * pixels[i] + 0.587 * pixels[i + 1] + 0.114 * pixels[i + 2]);
    }
    const avg = gray.reduce((a, b) => a + b, 0) / gray.length;
    const bits = gray.map(v => (v > avg ? 1 : 0));
    let hash = '';
    for (let i = 0; i < bits.length; i += 4) {
      const nibble = bits.slice(i, i + 4).reduce((a, b, j) => a | (b << (3 - j)), 0);
      hash += nibble.toString(16);
    }
    return hash;
  }

  function hamming (h1, h2) {
    let d = 0;
    const len = Math.min(h1.length, h2.length);
    for (let i = 0; i < len; i++) {
      const x = parseInt(h1[i], 16) ^ parseInt(h2[i], 16);
      d += x.toString(2).replace(/0/g, '').length;
    }
    return d;
  }

  async function loadIndex () {
    const url = mw.util.getUrl('MediaWiki:Gadget-LabelScan-index.json', { action: 'raw', ctype: 'application/json' });
    const res = await fetch(url);
    return res.json();
  }

  // === UI-Helfer ===========================================================
  function showPreview (file) {
    const prev = document.getElementById('ados-scan-preview');
    if (prev) prev.innerHTML = `<img alt="Vorschau" src="${URL.createObjectURL(file)}">`;
  }
  function setStatus (t) {
    const el = document.getElementById('ados-scan-status');
    if (el) el.textContent = t;
  }
  function renderResults (hits) {
    const box = document.getElementById('ados-scan-results');
    box.innerHTML = '';
    if (!hits.length) {
      box.innerHTML = '<div>Keine klaren Treffer gefunden.</div>';
      return;
    }
    hits.forEach(h => {
      box.innerHTML += `
        <div class="ados-hit">
          <a href="${mw.util.getUrl(h.title)}">
            <img src="${h.thumb}" alt="${h.title}">
            <br><b>${mw.html.escape(h.title)}</b>
          </a>
          <small>Distanz: ${h.dist}</small>
        </div>`;
    });
  }

  // === Haupt-Bindung =======================================================
  async function bind () {
    const runBtn = document.getElementById('ados-scan-run');
    const fileIn = document.getElementById('ados-scan-file');
    const bigBtn = document.getElementById('ados-scan-bigbtn');
    if (!runBtn || !fileIn) return;

    bigBtn?.addEventListener('click', () => fileIn.click());
    fileIn.addEventListener('change', e => { if (e.target.files[0]) showPreview(e.target.files[0]); });

    runBtn.addEventListener('click', async ev => {
      ev.preventDefault();
      const file = fileIn.files[0];
      if (!file) return alert('Bitte ein Bild wählen.');
      try {
        setStatus('Berechne Hash …');
        const img = await fileToImage(file);
        const ph = computePhash(img);

        setStatus('Lade Index …');
        const idx = await loadIndex();

        setStatus('Vergleiche …');
        const scored = idx.map(it => ({ ...it, dist: hamming(ph, it.phash) }));
        scored.sort((a, b) => a.dist - b.dist);

        const top = scored.slice(0, 5);
        renderResults(top);
        setStatus('Fertig.');
      } catch (e) {
        console.error('[LabelScan]', e);
        setStatus('Fehler bei Erkennung.');
      }
    });
  }

  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', bind);
  } else {
    bind();
  }
})();