Filipe: "auch die hintergrund bilder vom crew espace sollen anderes
aussehen, der community angepasst wie die zugangscode seite die ist
mega."
UMFAERBEN HAETTE HIER NICHT GEREICHT. Bei der Zugangswand war Rot das
Problem; innen zeigen die neun Buehnen das SPICY-MEDIA-LOGO selbst, in
`halle` und `arena` vielfach nebeneinander. Ein blaues fremdes Logo
waere schlimmer gewesen als ein rotes.
Die Motive gibt es laengst -- auf der oeffentlichen Dogfather-Seite:
studio/lounge "Team Dogi -- Familie. Treue. Zusammenhalt."
halle TEAM DOGI mit den Menschen des Teams
skyline/portal "Streamplan": Dogi und HasiDog auf der Buehne
arena "DogFathers Galerie": Filmrollen, ein Archiv
garage "Der Streamer": Filipe an seinem Platz
Fuer `garage` stand zuerst der Holo-Kontrollraum da -- grossartiges
Bild, aber darauf steht "WERDE MODI". Eine Einladung hinter dem
Aufgabenbrett von Leuten, die laengst dabei sind, liest sich falsch.
Ein Bild sagt etwas, auch wenn es nur Hintergrund ist.
DIE ABDUNKLUNG IST GERECHNET, NICHT GEWAEHLT
Mein erster fester Wert haette 18 von 27 Fassungen HELLER gemacht als
die, die sie ersetzen (114 gegen 67) -- ueber ihnen steht derselbe
kleine Text. Das Werkzeug misst jetzt jede Fassung gegen die bestehende
Buehne derselben Szene und senkt sie genau auf deren Wert.
Ueber eine KURVE statt eines schwarzen Schleiers: v' = 255*(v/255)^g
senkt die Lichter viel staerker als die Tiefen -- das Bild wird dunkler
und behaelt seine Zeichnung. Ein Schleier haette bei den hellsten
Motiven ueber 80 % gebraucht, und darunter liegt dann Nebel statt Bild
(derselbe Fehler wie beim Tresorbild).
Der erste Anlauf mit einer geschlossenen Formel war falsch: Die Kurve
wirkt auf jeden FARBKANAL, die Helligkeit ist eine gewichtete Summe der
drei, und L(v^g) ist nicht L(v)^g. Die Rechnung sagte 61,5 und heraus
kamen 67. Jetzt wird genaehert und nachgemessen, hoechstens sechsmal.
Beim Hochkant-Ausschnitt wird zusaetzlich das FENSTER GESUCHT: fuenf
Kandidaten, erst "dunkel genug", dann "am meisten Motiv". Die Mitte --
die das Haus sonst nimmt -- ist bei diesen Bildern das Hellste.
ZWEI FUNDE AUS DEM BILDSCHIRMFOTO, die nichts mit den Buehnen zu tun
hatten:
1. DIE KOPFLEISTE sagte "SPICY & DOGI · CREATOR WORKSPACE". Reitertitel,
Begruessung und Symbol waren laengst richtig -- nur die Zeile, die
man als Erstes ansieht, nicht. Der Grund: Auf Unterseiten traegt die
Leiste einen Rueckweg-Verweis, auf der Startseite blossen Text; mein
Code kannte nur die erste Bauform.
2. DAS CHILI-ZEICHEN kommt aus dem CSS als Bild, an drei Stellen
(Kopfleiste, Siegel im Ring, die schwebenden Wasserzeichen). Auf
crew. wird die Datei serverseitig durch den Husky ersetzt -- alle
drei auf einmal, und ohne Flackern, weil schon der erste Abruf das
richtige Bild bekommt.
GEMESSEN
pruef-crew-adresse 123 (statt 117): Fuer JEDE Buehne aus start.css --
die Namen werden dort GELESEN, nicht abgeschrieben --
gibt es auf crew. ein eigenes Motiv, auf workspace.
das alte, und die beiden sind nachweislich
verschiedene Dateien.
pruef-rollen 274 pruef-start-ansicht, pruef-zwischenspeicher 21
pruef-modi-wortleck 5 -- er hat wieder angeschlagen, zweimal auf
Kommentare, die ich selbst geschrieben hatte.
27 Fassungen, 3,5 MB (die bisherigen neun Szenen brauchen 7,1).
Co-Authored-By: Claude Opus 5 <[email protected]>
447 lines
20 KiB
JavaScript
447 lines
20 KiB
JavaScript
/* ===================================================================
|
|
Die Buehnen der Modi-App (crew.dogfather-universe.com, 10.09.2026)
|
|
|
|
Filipe: "auch die hintergrund bilder vom crew espace sollen anderes
|
|
aussehen, der community angepasst wie die zugangscode seite die ist
|
|
mega."
|
|
|
|
---------------------------------------------------------------------
|
|
WARUM UMFAERBEN HIER NICHT GENUEGT
|
|
|
|
Bei der Zugangswand hat es gereicht, die Szene ins Blau zu ziehen --
|
|
dort war Rot das Problem. INNEN ist es etwas anderes: Die neun
|
|
Buehnen zeigen nicht bloss rotes Licht, sondern das SPICY-MEDIA-LOGO
|
|
selbst, in `halle` und `arena` sogar vielfach nebeneinander. Ein
|
|
blaues Spicy-Logo waere schlimmer als ein rotes: Es saehe aus, als
|
|
haette jemand die falsche Marke eingefaerbt.
|
|
|
|
Die Modi-App braucht deshalb eigene Motive -- und die gibt es
|
|
laengst. Auf der oeffentlichen Dogfather-Seite liegen die
|
|
Community-Bilder, die genau das zeigen, was in dieser App passiert.
|
|
|
|
---------------------------------------------------------------------
|
|
WELCHES BILD WOHIN -- und das folgt dem, was auf der Seite passiert
|
|
|
|
studio Startseite "Team Dogi -- Familie. Treue.
|
|
Zusammenhalt." Filipe mit seinem
|
|
Husky. Der Eingang gehoert dem,
|
|
worum es geht.
|
|
lounge Chat, Community dasselbe Motiv: Wo geredet wird,
|
|
steht das Miteinander im Bild.
|
|
halle Angebote, Eingang, TEAM DOGI mit den Menschen des
|
|
Steckbrief Teams -- hier geht es um sie.
|
|
skyline Kalender "Streamplan": Dogi und HasiDog
|
|
auf der Buehne. Woertlich das
|
|
Thema der Seite.
|
|
portal Ideen-Board, dieselbe Buehne -- dort wird
|
|
Live-Ablauf gesendet, und dorthin gehen die
|
|
Ideen.
|
|
showbuehne Dashboard, Reports ebenfalls die Buehne.
|
|
arena Dateien, Wissen "DogFathers Galerie": Filmrollen
|
|
und Bildschirme. Ein Archiv sieht
|
|
so aus.
|
|
garage Aufgaben, Technik "Der Streamer": Filipe an seinem
|
|
Platz, Monitore, Ringlicht. Dort
|
|
wird gearbeitet.
|
|
|
|
ZUERST STAND HIER DER HOLO-KONTROLL-
|
|
RAUM, und er sah grossartig aus --
|
|
aber darauf steht "WERDE MODI".
|
|
Eine Einladung hinter dem
|
|
Aufgabenbrett von Leuten, die
|
|
laengst dabei sind, liest sich
|
|
falsch. Ein Bild sagt etwas, auch
|
|
wenn es nur Hintergrund ist.
|
|
wald Start-Check wieder das Familienmotiv; die
|
|
Seite gibt es fuer diese Rollen
|
|
gar nicht und sie ist nur da,
|
|
damit kein Rueckfall auf eine
|
|
Spicy-Buehne moeglich ist.
|
|
|
|
FUENF BILDER AUF NEUN PLAETZE, mit denselben Regeln wie im Haus:
|
|
Paare, die inhaltlich benachbart sind und nie nebeneinander auf einer
|
|
Seite stehen -- man sieht immer nur eins.
|
|
|
|
ALLE NEUN werden gebaut, auch die, die eine Modi-Rolle nie zu sehen
|
|
bekommt. Der Grund steht in `start.css`: Dort ist `studio` der
|
|
Rueckfall fuer jede Seite ohne eigene Szene. Eine Luecke hier hiesse,
|
|
dass irgendwann doch ein Spicy-Logo im Hintergrund der Modi-App
|
|
steht -- und zwar genau auf der Seite, an die niemand gedacht hat.
|
|
|
|
---------------------------------------------------------------------
|
|
DIE GEGENPROBE: NICHT HELLER ALS DAS, WAS SCHON LAEUFT
|
|
|
|
Ueber diesen Bildern steht Text. Die Dogfather-Motive sind heller als
|
|
die Spicy-Szenen -- weisse Schrift im Bild, helles Fell, Scheinwerfer.
|
|
Ein Bild, das gut aussieht und die Schrift darueber unlesbar macht,
|
|
ist der schnellste Weg zu einer Oberflaeche, die niemand benutzt.
|
|
|
|
Deshalb wird jede fertige Fassung gegen die BESTEHENDE gleicher Szene
|
|
und Groesse gehalten: Das obere Zehntel der Helligkeit im mittleren
|
|
Band -- dort, wo der Inhalt steht -- darf nicht heller sein als dort.
|
|
Das ist kein selbst gewaehlter Grenzwert, sondern der Stand, der seit
|
|
Wochen laeuft und den pruef-buehne an echten Bildpunkten gemessen hat.
|
|
|
|
Aufrufen mit: node tools/crew-buehnen.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 = process.env.BUEHNEN_ZIEL || join(WURZEL, "workspace", "assets", "img");
|
|
/* Zum Vergleichen immer die echten -- auch wenn das Ziel umgelenkt ist. */
|
|
const VERGLEICH = join(WURZEL, "workspace", "assets", "img");
|
|
|
|
const SZENEN = {
|
|
studio: "bg-community.jpg",
|
|
lounge: "bg-community.jpg",
|
|
wald: "bg-community.jpg",
|
|
halle: "bg-modis.jpg",
|
|
skyline: "bg-streamplan.jpg",
|
|
portal: "bg-streamplan.jpg",
|
|
showbuehne: "bg-streamplan.jpg",
|
|
arena: "bg-dogfather.jpg",
|
|
garage: "bg-streamer.jpg",
|
|
};
|
|
|
|
/* WIE STARK ABGEDUNKELT -- AUSGERECHNET, NICHT GEWAEHLT.
|
|
|
|
Erster Anlauf: ein fester Wert fuer alle, 0,34. Die Messung unten
|
|
hat ihn zerlegt -- achtzehn von siebenundzwanzig Fassungen waren
|
|
heller als die Buehne, die sie ersetzen, teils deutlich (114 gegen
|
|
67). Ueber ihnen steht derselbe kleine Text; ich haette Bilder
|
|
ausgeliefert, die gut aussehen und die Schrift darueber schlechter
|
|
lesbar machen.
|
|
|
|
EIN FESTER WERT KANN DAS AUCH NICHT LOESEN. Die fuenf Motive sind
|
|
verschieden hell: "Team Dogi" ist eine Nacht mit einem Ring, der
|
|
Holo-Kontrollraum ist von vorne bis hinten beleuchtet. Was fuer das
|
|
eine reicht, macht das andere zum Schemen -- und genau das ist beim
|
|
Tresorbild schon einmal passiert (0,62 uebernommen, man sah nichts
|
|
mehr).
|
|
|
|
Also wird je Fassung gerechnet: Erst ohne Abdunklung bauen und
|
|
messen, dann ausrechnen, wie viel Schwarz noetig ist, um genau auf
|
|
den Wert der bestehenden Buehne zu kommen -- und nur so viel.
|
|
Schwarz mit Deckkraft a macht aus L ein L*(1-a); daraus folgt
|
|
a = 1 - Ziel/Ist. So dunkel wie noetig, nicht dunkler. */
|
|
const GAMMA_MAX = 3.2; // darueber bleibt vom Motiv ein Schemen
|
|
|
|
/** Ein ERSTER Schaetzwert fuer die Kurve.
|
|
*
|
|
* Aus v' = 255*(v/255)^g folgt g = ln(Ziel/255) / ln(Ist/255) -- das
|
|
* gilt aber nur fuer GRAUE Bildpunkte. Die Kurve wirkt auf jeden
|
|
* Farbkanal einzeln, die Helligkeit ist eine gewichtete Summe der
|
|
* drei, und L(v^g) ist nicht L(v)^g, sobald ein Bildpunkt farbig ist.
|
|
*
|
|
* Genau daran ist der erste Anlauf gescheitert: Die Rechnung sagte
|
|
* 61,5 und heraus kamen 67. Deshalb ist das hier nur der Startwert --
|
|
* danach wird nachgemessen und nachgezogen, bis es stimmt. */
|
|
function gammaFuer(ist, ziel) {
|
|
if (!(ist > 0) || ist >= 255 || ziel <= 0) return 1;
|
|
const g = Math.log(ziel / 255) / Math.log(ist / 255);
|
|
return Math.min(Math.max(g, 1), GAMMA_MAX);
|
|
}
|
|
|
|
/**
|
|
* Baut so lange nach, bis der Spitzenwert das Ziel erreicht.
|
|
*
|
|
* ZWEI WERKZEUGE, IN DIESER REIHENFOLGE:
|
|
* 1. Die Kurve -- sie senkt die Lichter viel staerker als die Tiefen
|
|
* und laesst dem Bild seine Zeichnung.
|
|
* 2. Erst wenn die Kurve am Anschlag ist (GAMMA_MAX), kommt ein
|
|
* flacher schwarzer Schleier dazu. Er senkt alles gleich und legt
|
|
* Nebel ueber das Bild -- deshalb so spaet und so wenig wie
|
|
* moeglich.
|
|
*
|
|
* Hoechstens sechs Anlaeufe. Ein Naeherungsverfahren ohne Deckel ist
|
|
* eine Endlosschleife mit Umweg.
|
|
*/
|
|
async function annaehern(bauArgs, ziel) {
|
|
let gamma = 1, dunkel = 0;
|
|
let r = await bauen({ ...bauArgs, gamma, dunkel });
|
|
gamma = gammaFuer(r.spitze, ziel);
|
|
|
|
for (let runde = 0; runde < 6; runde++) {
|
|
r = await bauen({ ...bauArgs, gamma, dunkel });
|
|
if (r.spitze <= ziel + 1) break;
|
|
if (gamma < GAMMA_MAX) {
|
|
/* Noch einmal dieselbe Rechnung, diesmal vom gemessenen Stand
|
|
aus -- die Kurven verketten sich. */
|
|
gamma = Math.min(gamma * gammaFuer(r.spitze, ziel), GAMMA_MAX);
|
|
} else {
|
|
/* Kurve am Anschlag: den Rest flach wegnehmen. Schwarz mit
|
|
Deckkraft a macht aus L ein L*(1-a) -- und das gilt fuer
|
|
Farbe genauso wie fuer Grau, hier rechnet nichts falsch. */
|
|
dunkel = Math.min(1 - (1 - dunkel) * (ziel / r.spitze), 0.8);
|
|
}
|
|
}
|
|
return { ...r, gamma, dunkel };
|
|
}
|
|
|
|
/* WIE STARK GEKUEHLT.
|
|
|
|
Die Motive sind unterschiedlich warm: "Team Dogi" hat einen orangen
|
|
Ring, "TEAM DOGI mit den Menschen" ist Feuer. Nebeneinander in einer
|
|
App wirken sie wie fuenf verschiedene Programme.
|
|
|
|
Ein Drittel Blau ueber den Mischmodus "color" zieht sie zusammen,
|
|
ohne sie zu faelschen -- das Feuer bleibt Feuer, es glueht nur
|
|
kuehler. Ganz blau waere die Zugangswand, und die ist eine Buehne
|
|
fuer EIN Motiv; hier sind es fuenf, die zusammenpassen muessen. */
|
|
const KUEHLE = 0.32;
|
|
|
|
for (const datei of new Set(Object.values(SZENEN))) {
|
|
const p = join(QUELLE, datei);
|
|
if (!existsSync(p)) { console.error(`Fehlt: ${p}`); process.exit(1); }
|
|
}
|
|
|
|
const alsDaten = (p) => "data:image/jpeg;base64," + readFileSync(p).toString("base64");
|
|
|
|
const { chromium } = await import(
|
|
"file:///C:/Users/qciga/Documents/Obelix/Analyse/node_modules/playwright/index.mjs");
|
|
const browser = await chromium.launch();
|
|
const seite = await (await browser.newContext({ deviceScaleFactor: 1 })).newPage();
|
|
await seite.setViewportSize({ width: 400, height: 300 });
|
|
if (!existsSync(ZIEL)) mkdirSync(ZIEL, { recursive: true });
|
|
|
|
/** Baut eine Fassung und misst sie gleich mit. */
|
|
async function bauen({ quelle, breite, ausschnitt = null, gamma = 1, dunkel = 0, guete }) {
|
|
const roh = await seite.evaluate(async (a) => {
|
|
const i = new Image(); i.src = a.daten; await i.decode();
|
|
|
|
const qx = a.ausschnitt ? a.ausschnitt[0] * i.width : 0;
|
|
const qy = a.ausschnitt ? a.ausschnitt[1] * i.height : 0;
|
|
const qb = a.ausschnitt ? a.ausschnitt[2] * i.width : i.width;
|
|
const qh = a.ausschnitt ? a.ausschnitt[3] * i.height : i.height;
|
|
|
|
const zb = a.breite;
|
|
const zh = Math.round(zb * (qh / qb));
|
|
|
|
const c = document.createElement("canvas");
|
|
c.width = zb; c.height = zh;
|
|
const g = c.getContext("2d");
|
|
g.imageSmoothingQuality = "high";
|
|
|
|
g.filter = "contrast(1.06) saturate(1.04)";
|
|
g.drawImage(i, qx, qy, qb, qh, 0, 0, zb, zh);
|
|
g.filter = "none";
|
|
|
|
/* Kuehlen: Farbton und Saettigung von der Farbe, Helligkeit vom
|
|
Bild -- jede Kante und jeder Funke bleibt stehen. */
|
|
g.globalCompositeOperation = "color";
|
|
g.globalAlpha = a.kuehle;
|
|
g.fillStyle = "#2a5f8c";
|
|
g.fillRect(0, 0, zb, zh);
|
|
g.globalAlpha = 1;
|
|
g.globalCompositeOperation = "source-over";
|
|
|
|
/* Vignette: aussen dunkler, damit die Raender der Seite ruhig
|
|
bleiben und die Mitte trotzdem etwas zeigt. */
|
|
const v = g.createRadialGradient(zb / 2, zh / 2, Math.min(zb, zh) * 0.26,
|
|
zb / 2, zh / 2, Math.max(zb, zh) * 0.74);
|
|
v.addColorStop(0, "rgba(0,0,0,0)");
|
|
v.addColorStop(0.6, "rgba(0,0,0,.18)");
|
|
v.addColorStop(1, "rgba(0,0,0,.46)");
|
|
g.fillStyle = v;
|
|
g.fillRect(0, 0, zb, zh);
|
|
|
|
/* ===================================================================
|
|
ABDUNKELN MIT EINER KURVE, NICHT MIT EINEM SCHLEIER
|
|
|
|
Ein flaches Schwarz mit Deckkraft a macht aus jedem L ein
|
|
L*(1-a): Es senkt alles im selben Verhaeltnis. Bei den hellsten
|
|
Motiven waeren dafuer ueber 80 % noetig gewesen -- und darunter
|
|
liegt dann ein grauer Nebel statt eines Bildes. Genau das ist
|
|
beim Tresorbild schon einmal passiert.
|
|
|
|
Eine Gammakurve senkt die Lichter viel staerker als die Tiefen:
|
|
v' = 255 * (v/255)^g. Bei g = 1,8 faellt 200 auf 165 (-17 %),
|
|
aber 60 auf 19 (-69 %). Das Bild wird dunkler UND behaelt seine
|
|
Zeichnung -- die hellen Stellen bleiben die hellen, sie
|
|
schreien nur nicht mehr.
|
|
|
|
`g` wird nicht geraten: Aus dem gemessenen Spitzenwert p und dem
|
|
Ziel folgt g = ln(Ziel/255) / ln(p/255). Die Tabelle wird einmal
|
|
gefuellt und dann 256-fach nachgeschlagen statt millionenfach
|
|
gerechnet. */
|
|
if (a.gamma && a.gamma !== 1) {
|
|
const tabelle = new Uint8ClampedArray(256);
|
|
for (let v = 0; v < 256; v++) tabelle[v] = Math.round(255 * (v / 255) ** a.gamma);
|
|
const bd = g.getImageData(0, 0, zb, zh);
|
|
const px = bd.data;
|
|
for (let k = 0; k < px.length; k += 4) {
|
|
px[k] = tabelle[px[k]];
|
|
px[k + 1] = tabelle[px[k + 1]];
|
|
px[k + 2] = tabelle[px[k + 2]];
|
|
}
|
|
g.putImageData(bd, 0, 0);
|
|
}
|
|
|
|
/* Der flache Schleier -- nur, wenn die Kurve nicht gereicht hat. */
|
|
if (a.dunkel > 0) {
|
|
g.fillStyle = `rgba(0,0,0,${a.dunkel})`;
|
|
g.fillRect(0, 0, zb, zh);
|
|
}
|
|
|
|
/* MESSUNG: das obere Zehntel der Helligkeit im MITTLEREN BAND.
|
|
Dort steht der Inhalt; die Raender sind ohnehin dunkel und
|
|
wuerden den Wert schoenrechnen. */
|
|
const by = Math.round(zh * 0.18), bh = Math.round(zh * 0.64);
|
|
const d = g.getImageData(0, by, zb, bh).data;
|
|
const hell = [];
|
|
for (let k = 0; k < d.length; k += 4) {
|
|
hell.push(0.2126 * d[k] + 0.7152 * d[k + 1] + 0.0722 * d[k + 2]);
|
|
}
|
|
hell.sort((x, y) => x - y);
|
|
|
|
/* Die Streuung sagt, wie viel MOTIV in dem Ausschnitt steckt.
|
|
Eine leere dunkle Ecke ist zwar schoen dunkel -- und zeigt
|
|
nichts. */
|
|
let summe = 0, quadrate = 0;
|
|
for (const h of hell) { summe += h; quadrate += h * h; }
|
|
const mittel = summe / hell.length;
|
|
|
|
return {
|
|
bild: c.toDataURL("image/webp", a.guete),
|
|
spitze: hell[Math.floor(hell.length * 0.9)],
|
|
streuung: Math.sqrt(Math.max(quadrate / hell.length - mittel * mittel, 0)),
|
|
breite: zb, hoehe: zh,
|
|
};
|
|
}, { daten: alsDaten(quelle), breite, ausschnitt, gamma, dunkel, guete, kuehle: KUEHLE });
|
|
|
|
return { ...roh, puffer: Buffer.from(roh.bild.split(",")[1], "base64") };
|
|
}
|
|
|
|
/** Dieselbe Messung an einer BESTEHENDEN Datei -- der Massstab. */
|
|
async function messen(pfad) {
|
|
if (!existsSync(pfad)) return null;
|
|
return await seite.evaluate(async (b64) => {
|
|
const i = new Image(); i.src = "data:image/webp;base64," + b64; await i.decode();
|
|
const c = document.createElement("canvas");
|
|
c.width = i.width; c.height = i.height;
|
|
const g = c.getContext("2d");
|
|
g.drawImage(i, 0, 0);
|
|
const by = Math.round(i.height * 0.18), bh = Math.round(i.height * 0.64);
|
|
const d = g.getImageData(0, by, i.width, bh).data;
|
|
const hell = [];
|
|
for (let k = 0; k < d.length; k += 4) {
|
|
hell.push(0.2126 * d[k] + 0.7152 * d[k + 1] + 0.0722 * d[k + 2]);
|
|
}
|
|
hell.sort((x, y) => x - y);
|
|
return hell[Math.floor(hell.length * 0.9)];
|
|
}, readFileSync(pfad).toString("base64"));
|
|
}
|
|
|
|
const maengel = [];
|
|
let gebaut = 0, verglichen = 0, gesamt = 0;
|
|
|
|
console.log("\n--- Die neun Buehnen der Modi-App ---");
|
|
for (const [szene, datei] of Object.entries(SZENEN)) {
|
|
const quelle = join(QUELLE, datei);
|
|
const zeile = [];
|
|
|
|
for (const [name, breite, guete] of [
|
|
["uhd", 2400, 0.88],
|
|
["breit", 1600, 0.88],
|
|
/* HOCHKANT: die Mitte, hochkant beschnitten -- ein auf 9:16
|
|
gequetschtes Breitbild zeigt sonst nur Rand. Und dunkler: Auf dem
|
|
Handy liegen die kleinsten Texte der ganzen Anwendung genau in
|
|
der Bildmitte. Dieselbe Ueberlegung wie bei den Spicy-Buehnen,
|
|
dort am 07.09.2026 an echten Bildpunkten gemessen. */
|
|
["schmal", 900, 0.87],
|
|
]) {
|
|
/* Der Massstab: dieselbe Szene, dieselbe Groesse, wie sie seit
|
|
Wochen laeuft. Ohne ihn ist "nicht heller" eine Behauptung --
|
|
deshalb ist sein Fehlen ein FEHLER und kein Ueberspringen. */
|
|
const alt = await messen(join(VERGLEICH, `buehne-${szene}-${name}.webp`));
|
|
if (alt === null) {
|
|
maengel.push(`${szene}/${name}: keine bestehende Fassung zum Vergleichen`);
|
|
continue;
|
|
}
|
|
verglichen++;
|
|
|
|
/* ===================================================================
|
|
DER HOCHKANT-AUSSCHNITT WIRD GESUCHT, NICHT GESETZT
|
|
|
|
Das Haus schneidet fuer das Handy die MITTE heraus -- bei den
|
|
Spicy-Szenen richtig, dort steht das Logo mittig und der Grund
|
|
ringsum ist dunkel. Bei den Dogfather-Motiven ist die Mitte
|
|
genau das Hellste: "STREAMPLAN" in weisser Schrift, die
|
|
Scheinwerfer, der beleuchtete Kontrollraum.
|
|
|
|
Fuenf von neun Fassungen blieben deshalb auch mit 66 % Schwarz
|
|
zu hell -- und 81 % waeren noetig gewesen, also ein Schemen.
|
|
|
|
Jetzt werden fuenf Fenster durchprobiert und zwei Fragen
|
|
gestellt, in dieser Reihenfolge:
|
|
1. Wird es mit hoechstens 66 % Schwarz dunkel genug? (Pflicht)
|
|
2. In welchem steckt am meisten Motiv? (Kuer, ueber die
|
|
Streuung)
|
|
So viel Motiv wie moeglich, so dunkel wie noetig. Faellt Frage 1
|
|
ueberall durch, wird das dunkelste genommen UND gemeldet -- die
|
|
Messung ganz unten schlaegt dann ohnehin an. */
|
|
let ausschnitt = null;
|
|
if (name === "schmal") {
|
|
/* Das Fenster wird gesucht: erst dunkel genug, dann so viel
|
|
Motiv wie moeglich. */
|
|
let bester = null;
|
|
for (const x of [0, 0.11, 0.22, 0.33, 0.44]) {
|
|
const fenster = [x, 0, 0.56, 1];
|
|
const probe = await bauen({ quelle, breite, ausschnitt: fenster, guete });
|
|
const g = gammaFuer(probe.spitze, alt);
|
|
const passt = g < GAMMA_MAX;
|
|
if (!bester
|
|
|| (passt && !bester.passt)
|
|
|| (passt === bester.passt
|
|
&& (passt ? probe.streuung > bester.probe.streuung
|
|
: probe.spitze < bester.probe.spitze))) {
|
|
bester = { fenster, probe, passt };
|
|
}
|
|
}
|
|
ausschnitt = bester.fenster;
|
|
}
|
|
|
|
const r = await annaehern({ quelle, breite, ausschnitt, guete }, alt);
|
|
const { gamma, dunkel } = r;
|
|
writeFileSync(join(ZIEL, `crew-buehne-${szene}-${name}.webp`), r.puffer);
|
|
gebaut++;
|
|
gesamt += r.puffer.length;
|
|
|
|
/* Und danach NOCH EINMAL messen. Die Rechnung sagt, was noetig
|
|
waere; ob es gereicht hat, sagt nur das fertige Bild -- WebP
|
|
rundet, und die Deckelung bei 0,66 kann greifen. */
|
|
if (r.spitze > alt + 2) {
|
|
maengel.push(`${szene}/${name}: bleibt heller als die bestehende Buehne `
|
|
+ `(${r.spitze.toFixed(1)} gegen ${alt.toFixed(1)}, Kurve ${gamma.toFixed(2)}, `
|
|
+ `Schleier ${(dunkel * 100).toFixed(0)} %)`);
|
|
}
|
|
zeile.push(`${name} ${Math.round(r.puffer.length / 1024)} KB `
|
|
+ `· Kurve ${gamma.toFixed(2)}${dunkel ? ` +${(dunkel * 100).toFixed(0)} %` : ""}`
|
|
+ ` · ${r.spitze.toFixed(0)}/${alt.toFixed(0)}`
|
|
+ (ausschnitt ? ` · ab ${(ausschnitt[0] * 100).toFixed(0)} %` : ""));
|
|
}
|
|
console.log(` ${szene.padEnd(11)} ${datei.padEnd(22)} ${zeile.join(" · ")}`);
|
|
}
|
|
|
|
await browser.close();
|
|
|
|
/* Die Zahlen gehoeren in die Bedingung, nicht nur in den Bericht. */
|
|
const soll = Object.keys(SZENEN).length * 3;
|
|
if (gebaut !== soll) {
|
|
console.error(`\nFEHLER: ${gebaut} von ${soll} Fassungen erzeugt.`); process.exit(1);
|
|
}
|
|
if (verglichen !== soll) {
|
|
console.error(`\nFEHLER: nur ${verglichen} von ${soll} gegen eine bestehende Buehne gehalten.`);
|
|
process.exit(1);
|
|
}
|
|
if (maengel.length) {
|
|
console.error("\nFEHLER:\n " + maengel.join("\n ")); process.exit(1);
|
|
}
|
|
console.log(`\n${gebaut} Fassungen, ${Math.round(gesamt / 1024 / 1024 * 10) / 10} MB.`
|
|
+ ` Keine ist heller als die bestehende Buehne derselben Szene --`
|
|
+ ` der Text darueber bleibt also mindestens so lesbar wie bisher.`);
|