/* =================================================================== Baut die beiden Hintergrundbilder für den Workspace. Filipe: "der hintergrund soll genau so krass und speziell sein, benutz den hasen und husky." Warum die Bilder AUFBEREITET werden, statt sie einfach einzubinden: 1. Ein Foto hinter einem Arbeitswerkzeug macht Text unlesbar. Die Bilder werden deshalb stark abgedunkelt und leicht entsättigt -- sie sollen eine Stimmung sein, kein Motiv. 2. Sie bekommen einen weichen Rand. Ein hartes Rechteck sieht nach aufgeklebtem Bild aus; ein auslaufender Rand sieht nach Bühne aus. Der Verlauf wird HIER eingerechnet und nicht im Browser: Eine Maske über ein 900-Pixel-Bild kostet bei jedem Bildaufbau Rechenzeit. 3. Aus 290 KB PNG werden rund 40 KB WebP. Die Seite laedt auf jedem Aufruf schneller, und das Bild liegt im Zwischenspeicher. Gerechnet wird mit dem Browser, der ohnehin fuer die Pruefungen da ist -- kein zusaetzliches Bildprogramm, keine neue Abhaengigkeit, 0 EUR. Aufrufen mit: node tools/buehne-bauen.mjs =================================================================== */ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs"; import { join, dirname } from "node:path"; import { fileURLToPath } from "node:url"; const WURZEL = join(dirname(fileURLToPath(import.meta.url)), ".."); const QUELLE = join(WURZEL, "assets", "img"); const ZIEL = join(WURZEL, "workspace", "assets", "img"); if (!existsSync(ZIEL)) mkdirSync(ZIEL, { recursive: true }); const AUFTRAEGE = [ { /* Casper, der Husky. Neonblau auf Dunkel -- er passt farblich genau zur Oberfläche, anders als die warmen Fotomontagen. Sitzt unten links und schaut nach rechts in die Seite hinein. */ quelle: "hero-husky.png", ziel: "buehne-husky.webp", breite: 900, hoehe: 900, ausschnitt: [0, 0, 1, 1], spiegeln: false, /* Weich ausblenden nach rechts und nach oben -- dorthin, wo der Inhalt steht. */ verlauf: [ { art: "linear", von: [0.55, 0], nach: [1, 0], stopps: [[0, 0], [1, 1]] }, { art: "linear", von: [0, 0], nach: [0, 0.55], stopps: [[0, 1], [1, 0]] }, ], helligkeit: 0.62, saettigung: 1.0, }, { /* HasiDog. Das Ganzkoerperbild wird auf Kopf und Oberkoerper beschnitten -- die Fuesse traegt am unteren Bildrand niemand. */ quelle: "char-hasidog.jpg", ziel: "buehne-hasi.webp", breite: 760, hoehe: 900, ausschnitt: [0.02, 0.02, 0.96, 0.78], spiegeln: true, verlauf: [ { art: "linear", von: [0.45, 0], nach: [0, 0], stopps: [[0, 0], [1, 1]] }, { art: "linear", von: [0, 0], nach: [0, 0.5], stopps: [[0, 1], [1, 0]] }, ], helligkeit: 0.5, saettigung: 0.9, }, ]; 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(); await seite.setContent(""); for (const a of AUFTRAEGE) { const roh = readFileSync(join(QUELLE, a.quelle)); const art = a.quelle.endsWith(".png") ? "image/png" : "image/jpeg"; const daten = `data:${art};base64,${roh.toString("base64")}`; const webp = await seite.evaluate(async ({ daten, a }) => { const bild = new Image(); bild.src = daten; await bild.decode(); const c = document.createElement("canvas"); c.width = a.breite; c.height = a.hoehe; const g = c.getContext("2d"); const [ax, ay, aw, ah] = a.ausschnitt; g.filter = `brightness(${a.helligkeit}) saturate(${a.saettigung}) contrast(1.04)`; if (a.spiegeln) { g.translate(a.breite, 0); g.scale(-1, 1); } g.drawImage(bild, bild.naturalWidth * ax, bild.naturalHeight * ay, bild.naturalWidth * aw, bild.naturalHeight * ah, 0, 0, a.breite, a.hoehe); g.setTransform(1, 0, 0, 1, 0, 0); g.filter = "none"; /* Die weichen Raender. destination-out radiert dort, wo der Verlauf deckend ist -- so bleibt das Bild in der Ecke stehen und loest sich zur Seite hin auf. */ g.globalCompositeOperation = "destination-out"; for (const v of a.verlauf) { const gr = g.createLinearGradient( v.von[0] * a.breite, v.von[1] * a.hoehe, v.nach[0] * a.breite, v.nach[1] * a.hoehe); for (const [pos, deckung] of v.stopps) { gr.addColorStop(pos, `rgba(0,0,0,${deckung})`); } g.fillStyle = gr; g.fillRect(0, 0, a.breite, a.hoehe); } g.globalCompositeOperation = "source-over"; return c.toDataURL("image/webp", 0.74); }, { daten, a }); const puffer = Buffer.from(webp.split(",")[1], "base64"); writeFileSync(join(ZIEL, a.ziel), puffer); console.log(`${a.ziel.padEnd(20)} ${String(Math.round(roh.length / 1024)).padStart(4)} KB` + ` -> ${String(Math.round(puffer.length / 1024)).padStart(3)} KB`); } await browser.close(); console.log("\nFertig. Die Dateien liegen in workspace/assets/img/.");