#!/usr/bin/python3 # -*- coding: utf-8 -*- # Ausprobier-Apfelmännchen wie AUSPRAPF.BAS, nur alles mit GUI from tkinter import * # from enum import Enum, unique import threading import queue BREITE = 640 HOEHE = 480 ANZ_THREADS = 6 # Farbpalette fürs Apfelmännchen farbenVerlauf = ((193, 26, 93), (219, 76, 65), (215,119, 33), (223,171, 40), (223,213, 47), (158,200, 57), ( 90,187, 75), ( 58,167,101), ( 18,139,152), ( 6, 96,204), ( 8, 57,235), ( 58, 19,215), ( 98, 7,192), (149, 9,159), (180, 26,127)) farbeRechentMax = (6, 14, 12) # @unique # class Mauscursor(Enum): # kein=0 # fadenkreuz=1 # box=2 # kreuzchen=3 # gummiband=4 def farbe2rgb(rgbtupel): return "#%02x%02x%02x" % rgbtupel def werteInGUISetzen(): global xMin, xMax, yMin, yMax, z0, rechentiefe, render global xMinS, xMaxS, yMinS, yMaxS, z0rS, z0iS, rechentiefeS, renderS xMinS.set(str(xMin)) xMaxS.set(str(xMax)) yMinS.set(str(yMin)) yMaxS.set(str(yMax)) z0rS.set(str(z0.real)) z0iS.set(str(z0.imag)) rechentiefeS.set(str(rechentiefe)) renderS.set(str(render)) def validierenUebernehmen(): global xMin, xMax, yMin, yMax, z0, rechentiefe, render, statusMldS global xMinS, xMaxS, yMinS, yMaxS, z0rS, z0iS, rechentiefeS, renderS fehler="" try: xMin1 = float(xMinS.get()) xMax1 = float(xMaxS.get()) if xMax1 <= xMin1: fehler="xMax muss grösser als xMin sein" yMin1 = float(yMinS.get()) yMax1 = float(yMaxS.get()) if yMax1 <= yMin1: fehler="yMax muss grösser als yMin sein" z01 = complex(float(z0rS.get()), float(z0iS.get())) if abs(z01) >= 2.0: fehler="Betrag der z0-Konstante muss <2 sein" rechentiefe1 = int(rechentiefeS.get()) if rechentiefe1 < 1 or rechentiefe1 > 100000: fehler="Rechentiefe muss im Bereich 1-100000 liegen" render1 = int(renderS.get()) if render1 < 1 or render1 > 20: fehler="Renderqualität muss im Bereicht 1-20 liegen" except ValueError: fehler="Eingabefehler - bitte korrigieren" statusMldS.set(fehler) bOk = fehler=="" if bOk: xMin = xMin1 xMax = xMax1 yMin = yMin1 yMax = yMax1 z0 = z01 rechentiefe = rechentiefe1 render = render1 werteInGUISetzen() return bOk def setzAusschnUebers(bInit = False): global BREITE, HOEHE, xMin, xMax, yMin, yMax, z0, statusMldS if bInit or validierenUebernehmen(): if BREITE > HOEHE: offs = complex(2.05 * float(BREITE) / float(HOEHE), 2.05) else: offs = complex(2.05, 2.05 * float(HOEHE) / float(BREITE)) eckeRO = offs - z0 * z0 eckeLU = -offs - z0 * z0 xMin = eckeLU.real yMin = eckeLU.imag xMax = eckeRO.real yMax = eckeRO.imag werteInGUISetzen() statusMldS.set("Übersichtsauschnitt gesetzt") def ausproportionieren(): global BREITE, HOEHE, xMin, xMax, yMin, yMax, statusMldS if validierenUebernehmen(): if (yMax - yMin) * float(BREITE) < (xMax - xMin) * float(HOEHE): # Vertikal anpassen m = (yMax + yMin) / 2.0 w = (xMax - xMin) * float(HOEHE) / float(2 * BREITE) yMin = m - w yMax = m + w else: # Horizontal anpassen m = (xMax + xMin) / 2.0 w = (yMax - yMin) * float(BREITE) / float(2 * HOEHE) xMin = m - w xMax = m + w werteInGUISetzen() statusMldS.set("Ausschnitt ausproportioniert") class BildThread(threading.Thread): def run(self): global BREITE, HOEHE, xMin, xMax, yMin, yMax, z0, rechentiefe, render global farbenVerlauf, farbeRechentMax, ky, q xSchr = (xMax - xMin) / float(BREITE * render) ySchr = (yMax - yMin) / float(HOEHE * render) xBa = xMin + xSchr / 2.0 yBa = yMax - ySchr / 2.0 mutex = threading.Lock() ky1 = 0 while(ky1 < HOEHE): mutex.acquire() ky1 = ky if ky1 < HOEHE: ky += 1 mutex.release() if ky1 < HOEHE: # Wir haben eine "Aufgabe" zu erledigen pixz = [] for kx in range(BREITE): rgbKum = tuple(render * render // 2 for i in range(3)) for sy in range(render): for sx in range(render): c = complex(xBa + float(kx * render + sx) * xSchr, yBa - float(ky1 * render + sy) * ySchr) z = z0 i = 0 while(i < rechentiefe and abs(z) < 2.0): z = z * z + c i += 1 if (i == rechentiefe): f = farbeRechentMax else: f = farbenVerlauf[i % len(farbenVerlauf)] rgbKum = tuple(sum(i) for i in zip(rgbKum, f)) pixz.append("#%02x%02x%02x" % tuple(i // (render * render) for i in rgbKum)) q.put((pixz, ky1)) def berechneBild(): global bild, statusMldS, statusMldG, ky, q if validierenUebernehmen(): # Alles OK -> Fraktal berechnen mit Threads ky = 0 # Unterschied zu FreeBasic: Hauptthread mit mit dem GUI kommunizieren! # Gewählte Lösung hier: Berechnungsthreads geben ihr Resultat hier # dem Haupttread mittels Queue ab q = queue.Queue() threads = [] for i in range(ANZ_THREADS): t = BildThread() t.start() threads.append(t) for i in range(HOEHE): (pixelz, y) = q.get() bild.put("{" + " ".join(pixelz) + "}", (0, y)) statusMldS.set("Berechne ... %.2f%%" % (float(i * 100) / float(HOEHE))) statusMldG.update() for i in threads: i.join() statusMldS.set("* Fertig *") def posXY(xMaus, yMaus): global xMin, xMax, yMin, yMax, BREITE, HOEHE return complex(xMin + (xMax - xMin) * (float(xMaus) + 0.5) / float(BREITE), yMax - (yMax - yMin) * (float(yMaus) + 0.5) / float(HOEHE)) def regeneriereMauscursor(ereig): global aktrPosS, aktiPosS # global rechtangle, 1 zMaus = posXY(ereig.x, ereig.y) aktrPosS.set(str(zMaus.real)) aktiPosS.set(str(zMaus.imag)) w = Tk() w.title("Mandelbrotmenge") w.configure(padx=5, pady=5) canv = Canvas(w, width = BREITE, height = HOEHE, bg="#000000") bild = PhotoImage(width = BREITE, height = HOEHE) canv.create_image((0, 0), image = bild, anchor = NW) canv.grid(rowspan=3) lfAuss = LabelFrame(w, text="Ausschnitt", padx=5, pady=5) xMinS = StringVar() Label(lfAuss, text="XMin:").grid() Entry(lfAuss, textvariable=xMinS).grid(row=0, column=1) xMaxS = StringVar() Label(lfAuss, text="XMax:").grid(row=1) Entry(lfAuss, textvariable=xMaxS).grid(row=1, column=1) yMinS = StringVar() Label(lfAuss, text="YMin:").grid(row=2) Entry(lfAuss, textvariable=yMinS).grid(row=2, column=1) yMaxS = StringVar() Label(lfAuss, text="YMax:").grid(row=3) Entry(lfAuss, textvariable=yMaxS).grid(row=3, column=1) lfAuss.grid(row=0, column=1) lfParam = LabelFrame(w, text="Parameter", padx=5, pady=5) z0rS = StringVar() Label(lfParam, text="z0 Real:").grid() Entry(lfParam, textvariable=z0rS).grid(row=0, column=1, columnspan=2) z0iS = StringVar() Label(lfParam, text="z0 Imaginär:").grid(row=1) Entry(lfParam, textvariable=z0iS).grid(row=1, column=1, columnspan=2) rechentiefeS = StringVar() Label(lfParam, text="max. Rechentiefe").grid(row=2, columnspan=2) Entry(lfParam, textvariable=rechentiefeS, width=10).grid(row=2, column=2) renderS = StringVar() Label(lfParam, text="Renderqualität:").grid(row=3, columnspan=2) Entry(lfParam, textvariable=renderS, width=3).grid(row=3, column=2) lfParam.grid(row=1, column=1) fAktion = Frame(w, padx=5, pady=5) Button(fAktion, text="Bild berechnen", command=berechneBild).grid(columnspan=2) Button(fAktion, text="Werte zürucksetzen", command=werteInGUISetzen).grid(row=1, columnspan=2) Button(fAktion, text="Ausproportionieren", command=ausproportionieren).grid(row=2, columnspan=2) Button(fAktion, text="Übersichtsbild", command=setzAusschnUebers).grid(row=3, columnspan=2) mausOpS = StringVar() mausOpS.set("Einzoomen") Label(fAktion, text="Mausmodus:").grid(row=4) om = OptionMenu(fAktion, mausOpS, "Einzoomen", "Auszoomen", "Schwenken (Pan)") om.configure(width=15) om.grid(row=4, column=1) fAktion.grid(row=2, column=1) fStatus = Frame(w, padx=5, pady=5) statusMldS = StringVar() Label(fStatus, text="Statusmeldung:").pack(side=LEFT) statusMldG = Entry(fStatus, textvariable=statusMldS, state="readonly", width="40") statusMldG.pack(side=LEFT) aktrPosS = StringVar() Label(fStatus, text="Aktuelle Pos.:").pack(side=LEFT) Entry(fStatus, textvariable=aktrPosS, state="readonly").pack(side=LEFT) aktiPosS = StringVar() Label(fStatus, text="+i*").pack(side=LEFT) Entry(fStatus, textvariable=aktiPosS, state="readonly").pack(side=LEFT) fStatus.grid(row=3, columnspan=2) # Werte beim Programmstart z0 = complex(0.4, 0.7) rechentiefe = 256 render = 1 setzAusschnUebers(True) canv.bind("", regeneriereMauscursor) print("Fertig") mainloop() print("Ende")