Files
provisionsmanager95/build/make_tipp_bild.py
T
adminandClaude Opus 5.5 26ac90bb0f Popups kommen nach Klick woanders / Alt+Tab wieder nach vorn; Büroklammer im Tipp des Tages
Fehler: War ein Popup offen und man klickte in ein anderes Programm, ließ sich das Popup über
Hauptfenster, Taskleiste oder Alt+Tab nicht mehr nach vorn holen.
Ursache: Rahmenlose Tk-Fenster mit transient() legt Tk unter Windows als Kindfenster des Desktops
(WS_CHILD) an – ohne Besitzer, daher kam beim Zurückholen nur das (gesperrte) Hauptfenster nach vorn.

- Popups (Dialoge, Rückfragen, Rundgang) ohne transient(), stattdessen Windows-Besitzer +
  Sperre des aufrufenden Fensters wie bei echten modalen Windows-Dialogen (gui/win32.py)
- Rundgang: neue Sprechblase erscheint vor dem Entfernen der alten (sonst springt Windows weg)
- Alt+F4 und Modalsperre robust gegen Timing (Sperre wird bei Bedarf nachgeholt)
- Tests (nur unter Windows/Wine): Besitzer, Sperre, Stapelreihenfolge nach „woanders klicken“
  und Zurückholen, verschachtelte Rückfragen, Rundgang
- Tipp des Tages mit freigestelltem Büroklammer-Bild (build/make_tipp_bild.py), in der .exe
  enthalten und vom Selbsttest geprüft

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-02 11:26:17 +01:00

62 lines
2.6 KiB
Python

"""Erzeugt provtool/gui/bilder/tipp.png (freigestellt, verkleinert, ohne Metadaten) aus dem Foto
für den „Tipp des Tages“. Nur zum Entwickeln nötig (Pillow): python build/make_tipp_bild.py <foto>"""
import sys
from collections import deque
from pathlib import Path
from PIL import Image
ZIEL_HOEHE = 170
ROOT = Path(__file__).resolve().parents[1]
def freistellen(im: Image.Image, schwelle: int = 232, min_flaeche: int = 6000) -> Image.Image:
"""Weißen Hintergrund transparent machen: alle hellen Flächen, die den Rand berühren ODER groß
sind (z. B. das Innere der Büroklammer-Schlaufen). Kleine helle Flächen (Augenweiß) bleiben."""
im = im.convert("RGBA")
w, h = im.size
px = im.load()
hell = lambda p: p[0] >= schwelle and p[1] >= schwelle and p[2] >= schwelle # noqa: E731
gesehen = bytearray(w * h)
for sy in range(h):
for sx in range(w):
if gesehen[sy * w + sx] or not hell(px[sx, sy]):
continue
flaeche, rand, q = [], False, deque([(sx, sy)])
gesehen[sy * w + sx] = 1
while q:
x, y = q.popleft()
flaeche.append((x, y))
if x in (0, w - 1) or y in (0, h - 1):
rand = True
for nx, ny in ((x + 1, y), (x - 1, y), (x, y + 1), (x, y - 1)):
if 0 <= nx < w and 0 <= ny < h and not gesehen[ny * w + nx] and hell(px[nx, ny]):
gesehen[ny * w + nx] = 1
q.append((nx, ny))
if rand or len(flaeche) >= min_flaeche:
for x, y in flaeche:
px[x, y] = (255, 255, 255, 0)
return im.crop(im.getbbox())
def main(foto: str, ziel: Path, modus: str = "lanczos"):
im = freistellen(Image.open(foto))
faktor = ZIEL_HOEHE / im.height
groesse = (max(1, round(im.width * faktor)), ZIEL_HOEHE)
if modus == "raster": # erst auf das Pixel-Raster (ca. 8 px) mitteln, dann hochskalieren
klein = im.resize((im.width // 8, im.height // 8), Image.BOX)
im = klein.resize(groesse, Image.NEAREST)
else:
im = im.resize(groesse, Image.LANCZOS)
# Halbtransparente Randpixel hart machen (Tk/Win95-Optik, keine weißen Säume)
alpha = im.getchannel("A").point(lambda a: 255 if a >= 128 else 0)
im.putalpha(alpha)
ziel.parent.mkdir(parents=True, exist_ok=True)
im.save(ziel, optimize=True) # PNG ohne EXIF-Metadaten
print(ziel, im.size)
if __name__ == "__main__":
main(sys.argv[1], Path(sys.argv[2]) if len(sys.argv) > 2 else ROOT / "provtool" / "gui" / "bilder" / "tipp.png",
sys.argv[3] if len(sys.argv) > 3 else "lanczos")