// Das PNG eines Community-Logos als Bild einer Kachel: laden, auf die Pixelgrenze prüfen und auf der Bildfläche zeigen. Die Object-URL // gehört dem Aufrufer, der sie über die zurückgegebene Funktion freigibt. Fremde Dateien sind ungeprüft: Bilder über der Grenze oder // nicht lesbare zeigt es nicht (neutraler Platzhalter), es stürzt nie ab. import { TONE_COLOR } from '../card/config'; import { el } from '../editor/dom'; import { MAX_LOGO_PIXELS } from './logo-asset'; import type { CommunityLogo } from './logos'; /** * Lädt die Object-URL in ein `Image` und lehnt ab, was sich nicht lesen lässt oder mehr als `MAX_LOGO_PIXELS` Pixel hat (so wie * `checkGalleryImage` die Galerie-Bilder prüft): ein winziges, stark komprimiertes PNG kann sonst Gigabytes an Pixeln enthalten * und den Tab einfrieren. Die Maße sind nach dem Laden bekannt, das Bild ist aber noch nicht dekodiert. */ function checkImage(url: string): Promise { return new Promise((resolve, reject) => { const probe = new Image(); probe.onload = () => { const { naturalWidth: w, naturalHeight: h } = probe; if (w > 0 && h > 0 && w * h <= MAX_LOGO_PIXELS) resolve(); else reject(new Error('Das Logo ist zu groß.')); }; probe.onerror = () => reject(new Error('Das Logo konnte nicht gelesen werden.')); probe.src = url; }); } /** Einfarbiges Logo: nur die Deckkraft des PNGs zählt (Maske auf dessen Object-URL), gefüllt in Weiß wie auf der dunklen Karte. */ function maskOf(url: string): HTMLElement { const mask = el('span', { class: 'logo-tile-mask', 'aria-hidden': 'true' }); mask.style.setProperty('--logo-url', `url("${url}")`); mask.style.setProperty('background-color', TONE_COLOR.light); return mask; } /** * Zeigt das Logo auf `art` (dekorativ: mehrfarbig als Bild, einfarbig als weiße Maske) und setzt dort `data-state`: `loading`, dann * `ready` oder `unusable` (Ladefehler, nicht lesbar, zu groß: die Fläche bleibt leer, der Platzhalter kommt aus dem Stylesheet). * `load` holt die Datei. Die zurückgegebene Funktion gibt die Object-URL frei; eine Datei, die erst danach eintrifft, wird nicht mehr * angezeigt und ihre URL sofort wieder freigegeben. */ export function showLogoImage(art: HTMLElement, logo: CommunityLogo, load: (logo: CommunityLogo) => Promise): () => void { let shown: string | null = null; let released = false; art.setAttribute('data-state', 'loading'); void (async () => { let url: string | null = null; try { const blob = await load(logo); if (released) return; url = URL.createObjectURL(blob); await checkImage(url); if (!released) { shown = url; art.replaceChildren(logo.mono ? maskOf(url) : el('img', { class: 'logo-tile-img', src: url, alt: '', draggable: 'false' })); art.setAttribute('data-state', 'ready'); return; } } catch { if (!released) art.setAttribute('data-state', 'unusable'); } if (url) URL.revokeObjectURL(url); // nicht angezeigt (abgelehnt oder die Kachel ist inzwischen weg) })(); return () => { released = true; if (shown) URL.revokeObjectURL(shown); shown = null; }; }