Files
dogfather-universe/pruef-bild-vorbereiten.mjs
T
DogFatherGitandClaude Opus 5 5b2a1daabe Öffentliche Avatar-Uploads vor dem Absenden verkleinern (1,1 MB -> ~40 KB)
Der Performance-Check (pruef-tempo.mjs, neu) fand als größte Einzel-
ressource ein 1,1-MB-Profilbild, ausgeliefert an jeden Besucher der
Startseite und von stimmen.html -- angezeigt wird es handgroß.

Ursache: Das öffentliche Stimmen-Formular (stimmen.js) lud Profilbilder
ROH hoch. Die Bildaufbereitung vom 27.08. bekam nur die Verwaltung, nicht
das öffentliche Formular. So landete das Kamera-Foto in voller Auflösung
auf dem Server.

- bild-vorbereiten.js (neu): die Verkleinerungs-Funktion, jetzt einmal
  und parametrisierbar (maxBreite). Verwaltung nutzt weiter 1600px,
  Avatare 512px. Test pruef-bild-vorbereiten.mjs: 8/8, u.a. 512er-Avatar
  ~40 KB statt >1 MB.
- stimmen.js: verkleinert vor dem Upload (window.bildVorbereiten mit
  maxBreite 512); fällt die Funktion aus, wird das Original genommen --
  kein Upload darf daran scheitern.
- stimmen.html: lädt bild-vorbereiten.js; veralteten Kommentar
  richtiggestellt (behauptete "kein Upload", obwohl es seit 21.08. einen
  gibt -- mit Bremse, Typ-/Größenlimit, Freigabe-Pflicht).

Zwei neue Checks als bleibende Absicherung mit committet: pruef-links.mjs
(41 interne Ziele, 0 kaputt) und pruef-assets.mjs (67 Ressourcen, 0 fehlen).

NOCH OFFEN: verwaltung.html hat noch eine eigene, identische Inline-Kopie
der Funktion -- die Zusammenführung ist ein eigener, testbarer Schritt
(das Inline-Skript dort ist groß und die Verwaltung hinter dem Gate schwer
live zu testen). Das bestehende 1,1-MB-Bild auf dem Server bleibt, bis es
neu hochgeladen/ersetzt wird.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-08-27 15:17:30 +02:00

92 lines
4.0 KiB
JavaScript

/* =====================================================================
Verkleinert bild-vorbereiten.js richtig?
Läuft in einem echten Browser (Canvas/Image gibt es nur dort). Erzeugt
Testbilder verschiedener Größe/Format und prüft das Verhalten, auf das
es ankommt: große Bilder werden auf die vorgegebene Breite gebracht,
kleine bleiben unangetastet, große PNGs werden zu JPEG, und die
Avatar-Grenze (512) macht ein Foto deutlich kleiner als die
Verwaltungs-Grenze (1600).
===================================================================== */
import { chromium } from "playwright";
import { readFileSync } from "node:fs";
const quelle = readFileSync("assets/js/bild-vorbereiten.js", "utf8");
const browser = await chromium.launch();
const seite = await browser.newPage();
await seite.setContent("<!doctype html><html><body></body></html>");
await seite.addScriptTag({ content: quelle });
let ok = 0, fehl = 0;
const pruefe = (name, gut, info) => {
if (gut) { ok++; console.log(" ok " + name + (info ? " -> " + info : "")); }
else { fehl++; console.log(" FEHL " + name + (info ? " -> " + info : "")); }
};
/* Hilfsfunktion im Browser: erzeugt ein Testbild als File, jagt es durch
bildVorbereiten und meldet Maße/Größe/Typ vorher und nachher. */
const test = (breite, hoehe, typ, opt) => seite.evaluate(async ([breite, hoehe, typ, opt]) => {
const c = document.createElement("canvas");
c.width = breite; c.height = hoehe;
const ctx = c.getContext("2d");
/* Echtes Zufallsrauschen, damit die Testbilder realistisch groß werden
(ein Muster oder eine Fläche würde PNG so stark komprimieren, dass die
"großes PNG"-Grenze von 400 KB nie erreicht wird -- dann bliebe der
PNG->JPEG-Pfad ungetestet). */
const img = ctx.createImageData(breite, hoehe);
for (let i = 0; i < img.data.length; i += 4) {
img.data[i] = Math.random() * 256; img.data[i+1] = Math.random() * 256;
img.data[i+2] = Math.random() * 256; img.data[i+3] = 255;
}
ctx.putImageData(img, 0, 0);
const blob = await new Promise((f) => c.toBlob(f, typ, 0.95));
const datei = new File([blob], "test." + (typ === "image/png" ? "png" : "jpg"), { type: typ });
const raus = await window.bildVorbereiten(datei, opt);
/* Maße des Ergebnisses auslesen. */
const masse = await new Promise((f) => {
const b = new Image();
b.onload = () => f({ w: b.naturalWidth, h: b.naturalHeight });
b.src = URL.createObjectURL(raus);
});
return { vorherBytes: datei.size, nachherBytes: raus.size, w: masse.w, h: masse.h, typ: raus.type };
}, [breite, hoehe, typ, opt]);
console.log("\n=== Großes Foto, Verwaltungs-Grenze (1600) ===");
{
const r = await test(2400, 1350, "image/jpeg", { maxBreite: 1600 });
pruefe("wird auf 1600 Breite gebracht", r.w === 1600, `${r.w}px breit`);
pruefe("wird kleiner", r.nachherBytes < r.vorherBytes, `${Math.round(r.vorherBytes/1024)} -> ${Math.round(r.nachherBytes/1024)} KB`);
}
console.log("\n=== Großes Foto, Avatar-Grenze (512) ===");
{
const r = await test(2400, 1350, "image/jpeg", { maxBreite: 512 });
pruefe("wird auf 512 Breite gebracht", r.w === 512, `${r.w}px breit`);
pruefe("Avatar ist DEUTLICH kleiner als 1600er", r.nachherBytes < 80 * 1024, `${Math.round(r.nachherBytes/1024)} KB`);
}
console.log("\n=== Kleines Bild bleibt unangetastet ===");
{
const r = await test(300, 300, "image/jpeg", { maxBreite: 512 });
pruefe("300px-Bild behält 300px", r.w === 300, `${r.w}px`);
}
console.log("\n=== Großes PNG wird zu JPEG ===");
{
const r = await test(2000, 1200, "image/png", { maxBreite: 1600 });
pruefe("Format wird JPEG", r.typ === "image/jpeg", r.typ);
pruefe("auf 1600 gebracht", r.w === 1600, `${r.w}px`);
}
console.log("\n=== Seitenverhältnis bleibt erhalten ===");
{
const r = await test(1600, 900, "image/jpeg", { maxBreite: 800 });
const verh = (r.w / r.h).toFixed(2);
pruefe("16:9 bleibt 16:9", verh === "1.78", `${r.w}x${r.h} (${verh})`);
}
await browser.close();
console.log(`\n===== ${ok} bestanden, ${fehl} fehlgeschlagen =====`);
process.exitCode = fehl ? 1 : 0;