Windows-Build / windows-exe (push) Failing after 3m10s
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>
62 lines
2.6 KiB
Python
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")
|