/* ===================================================================== AUS EINEM LOGO EINE MASKE MACHEN (19.09.2026) Filipe: „die kachel links soll dogfather logo als hintergrund haben und rechts das logo von vanvan. die logos sollen auch richtig geil im hintegrrund sein. die sollen paar mal sein in der kacheln und so wie schweben, sich ganz leicht bewegen." WARUM EINE MASKE UND NICHT DAS BILD SELBST: Ein Logo als Hintergrundbild bringt seine eigenen Farben mit. Beide hier sind schwarz auf weiss -- auf einer dunklen Kachel waere das entweder ein weisser Kasten (VanVan, kein Alphakanal) oder ein schwarzer Fleck, den man nicht sieht (DogFather). Umfaerben liesse sich das nur mit Filtern, die man raten muss. Eine Maske traegt gar keine Farbe. Sie sagt nur, WO etwas zu sehen ist -- die Farbe kommt aus dem CSS. Damit laesst sich dasselbe Logo in Babyblau, in Lila und morgen in Gruen zeichnen, ohne eine neue Datei. WIE DIE MASKE ENTSTEHT: Alpha = (1 - Helligkeit) x vorhandenes Alpha. Wo das Logo schwarz ist, wird die Maske dicht; wo es weiss oder durchsichtig ist, bleibt sie offen. Die Zeichnung bleibt dadurch erhalten -- eine reine Silhouette waere ein Klecks. UND KLEINER. Das Original von VanVan ist 387 KB. Fuer ein Hintergrundmuster, das nie groesser als ein paar hundert Pixel erscheint, ist das Verschwendung: Es wird auf 320 px gerechnet, und uebrig bleiben wenige Kilobyte. Aufrufen mit: node tools/logo-maske.mjs ===================================================================== */ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs"; import { join, dirname } from "node:path"; import { fileURLToPath } from "node:url"; const HIER = dirname(fileURLToPath(import.meta.url)); const WURZEL = join(HIER, ".."); const ZIEL = join(WURZEL, "assets", "img", "marke"); /** Woraus wird was gemacht. Beide Quellen sind schwarz-weiss. */ const AUFTRAEGE = [ { quelle: join(WURZEL, "assets/img/marke/logo-kopf-frei.png"), ziel: join(ZIEL, "maske-dogi.png"), kante: 320, was: "Der Huskykopf" }, { quelle: join(WURZEL, "assets/img/marke/vanvan-logo-quelle.png"), ziel: join(ZIEL, "maske-vanvan.png"), kante: 320, was: "Van's DIY & Bastelbedarf" }, ]; const fehlend = AUFTRAEGE.filter((a) => !existsSync(a.quelle)); if (fehlend.length) { console.error("KANN NICHT ARBEITEN -- diese Vorlagen fehlen:"); for (const f of fehlend) console.error(" " + f.quelle); console.error(""); console.error("Das VanVan-Logo liegt unter https://vans-diy-bastelbedarf.com/logo.png"); console.error("und gehoert VanVan. Es wird hier nur verkleinert und entfaerbt."); process.exit(2); } const { chromium } = await import( "file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs"); const browser = await chromium.launch(); const seite = await browser.newPage(); for (const auftrag of AUFTRAEGE) { const roh = readFileSync(auftrag.quelle).toString("base64"); const ergebnis = await seite.evaluate(async ([daten, kante]) => { const bild = new Image(); bild.src = "data:image/png;base64," + daten; await bild.decode(); /* Quadratisch und mittig -- beide Logos sind schon nahezu quadratisch, aber "nahezu" ist keine Zusicherung. */ const s = Math.max(bild.width, bild.height); const gross = document.createElement("canvas"); gross.width = s; gross.height = s; const gk = gross.getContext("2d"); gk.drawImage(bild, (s - bild.width) / 2, (s - bild.height) / 2); const d = gk.getImageData(0, 0, s, s); const p = d.data; for (let i = 0; i < p.length; i += 4) { /* Helligkeit nach Wahrnehmung, nicht als Mittelwert: Gruen wiegt schwerer als Blau. Bei Schwarz-Weiss macht das keinen Unterschied, bei einem spaeter farbigen Logo schon. */ const hell = (0.2126 * p[i] + 0.7152 * p[i + 1] + 0.0722 * p[i + 2]) / 255; const alpha = p[i + 3] / 255; const neu = Math.round((1 - hell) * alpha * 255); p[i] = p[i + 1] = p[i + 2] = 255; // Farbe egal, die Maske traegt nur Alpha p[i + 3] = neu; } gk.putImageData(d, 0, 0); /* Verkleinern in EINEM Schritt mit hoher Glaettung. Mehrere Halbierungen waeren schaerfer, aber hier ist ein weiches Hintergrundmuster gewollt. */ const klein = document.createElement("canvas"); klein.width = kante; klein.height = kante; const kk = klein.getContext("2d"); kk.imageSmoothingEnabled = true; kk.imageSmoothingQuality = "high"; kk.drawImage(gross, 0, 0, kante, kante); /* Wie viel ist ueberhaupt sichtbar? Eine Maske, die fast leer ist, waere ein stiller Fehlschlag -- man sieht auf der Kachel nichts und sucht im CSS. */ const kd = kk.getImageData(0, 0, kante, kante).data; let sichtbar = 0; for (let i = 3; i < kd.length; i += 4) if (kd[i] > 24) sichtbar++; return { png: klein.toDataURL("image/png").split(",")[1], anteil: sichtbar / (kante * kante), }; }, [roh, auftrag.kante]); const bytes = Buffer.from(ergebnis.png, "base64"); if (!existsSync(ZIEL)) mkdirSync(ZIEL, { recursive: true }); writeFileSync(auftrag.ziel, bytes); const vorher = readFileSync(auftrag.quelle).length; const anteil = (ergebnis.anteil * 100).toFixed(1); const warnung = ergebnis.anteil < 0.03 || ergebnis.anteil > 0.9 ? " ACHTUNG: sieht nicht nach einem Logo aus" : ""; console.log(`${auftrag.was}:`); console.log(` ${Math.round(vorher / 1024)} KB -> ${Math.round(bytes.length / 1024)} KB` + ` (${auftrag.kante} px, ${anteil} % gedeckt)${warnung}`); } await browser.close(); console.log(""); console.log("Fertig. Die Masken tragen keine Farbe -- die kommt aus dem CSS.");