Canvas-Element
Einführung: Linien und Rechtecke
Bei einem Canvas handelt es sich um ein grafisches Element, das ein Bestandteil von HTML5 ist und mit HTML5 eingeführt worden ist. Mit Canvas ist es möglich Bitmap-Grafiken (Grafiken, Graphen und einfache Animationen) automatisch, - also mittels Skriptsprachen - zu erzeugen. Analog wird ein Canvas auch unter Tkinter genutzt. Das Canvas-Element unter Tkinter stellt verschiedene grafische Möglichkeiten zur Verfügung, wie zum Beispiel Linien, Kreise, Bilder und sogar die Mgölichkeit andere Widgets einzubauen. .
Zur Demonsration der Möglichkeiten zeigen wir in unserem ersten Beispiel wie wir eine Linie (mathematisch auch als "Strecke" bezeichnet) zeichnen.
Dazu benutzen wir die Methode create_line. Mit create_line(coords, options) können wir eine gerade Linie zeichnen. Die Koordinaten "coords" werden als vier ganze Zahlen (Integers) angegeben: x1, y1, x2, y2 Das bedeutet, dass die Linie vom Punkt (x1, y1) zum Punkt (x2, y2) verläuft.
Nach diesen Koordinaten folgt eine durch Kommas getrennte Liste von zusätzlichen Parametern, die häufig leer ist. In unserem Fall setzen wir die Farbe der Linie auf das spezielle Grün unserer Python-Webseite: fill="#476042"
Wir halten unser erstes Beispiel bewusst einfach. Wir erzeugen ein Canvas-Element und zeichnen in dieses eine gerade horizontale Linie. Diese Linie zerteilt das Canvas vertikal in zwei Gebiete.
Die Umwandlung (casting) in einen Integer-Typ in der Anweisung "y = int(canvas_height / 2)" ist eigentlich überflüssig, weil die Methode auch mit Fließkommazahlen (floats) zurecht kommt, d.h. sie werden automatisch in ganze Zahlen gewandelt. Der Programmcode für unser Beispiel sieht nun wie folgt aus:
from tkinter import * master = Tk() canvas_width = 80 canvas_height = 40 w = Canvas(master, width=canvas_width, height=canvas_height) w.pack() y = int(canvas_height / 2) w.create_line(0, y, canvas_width, y, fill="#476042") mainloop()
Starten wir dieses kleine Skript, unter Benutzung von Python 3, erhalten wir folgendes Fenster als Ausgabe:
Um Rechtecke zu generieren können wir die Methode create_rectangle(coords, options) benutzen. Coords wird wieder durch zwei Punkte definiert. Der erste Punkt bestimmte die linke obere Ecke und der zweite Punkt die rechte untere Ecke des Rechtecks.
Das obige Fenster wird durch folgenden Python-Tkinter-Code erzeugt:
from tkinter import * master = Tk() w = Canvas(master, width=200, height=100) w.pack() w.create_rectangle(50, 20, 150, 80, fill="#476042") w.create_rectangle(65, 35, 135, 65, fill="yellow") w.create_line(0, 0, 50, 20, fill="#476042", width=3) w.create_line(0, 100, 50, 80, fill="#476042", width=3) w.create_line(150,20, 200, 0, fill="#476042", width=3) w.create_line(150, 80, 200, 100, fill="#476042", width=3) mainloop()
Die folgende Grafik soll dazu dienen das Verständnis bezüglich der Eckpunkte bei Rechtecken bzw. Endpunkten bei Linien anhand unseres vorigen Beispieles zu vertiefen:
Text im Canvas
Nun wollen wir zeigen, wie man Text in einem Canvas ausgibt. Dazu werden wir das vorige Beispiel zu diesem Zweck erweitern und modifizieren. Mit Hilfe der Methode create_text() kann man Text in ein Canvas-Objekt schreiben. Die ersten beiden Parameter sind die x und die y Position dieses Text-Objektes. Standardmäßig wird der Text über dieser Position zentriert. Dieses Standardverhalten (Default-Verhalten) kann durch die anchor-Option verändert werden. Soll zum Beispiel durch x und y die linke obere Ecke definiert werden, so setzen wir ancho auf NW (north west). Mit Hilfe des Schlüsselwort-Parameters "text" können wir den eigentlichen Text definieren, der im Canvas dargestellt werden soll.from tkinter import * canvas_width = 200 canvas_height = 100 colours = ("#476042", "yellow") box=[] for ratio in ( 0.2, 0.35 ): box.append( (canvas_width * ratio, canvas_height * ratio, canvas_width * (1 - ratio), canvas_height * (1 - ratio) ) ) master = Tk() w = Canvas(master, width=canvas_width, height=canvas_height) w.pack() for i in range(2): w.create_rectangle(box[i][0], box[i][1],box[i][2],box[i][3], fill=colours[i]) w.create_line(0, 0, # origin of canvas box[0][0], box[0][1], # coordinates of left upper corner of the box[0] fill=colours[0], width=3) w.create_line(0, canvas_height, # lower left corner of canvas box[0][0], box[0][3], # lower left corner of box[0] fill=colours[0], width=3) w.create_line(box[0][2],box[0][1], # right upper corner of box[0] canvas_width, 0, # right upper corner of canvas fill=colours[0], width=3) w.create_line(box[0][2], box[0][3], # lower right corner pf box[0] canvas_width, canvas_height, # lower right corner of canvas fill=colours[0], width=3) w.create_text(canvas_width / 2, canvas_height / 2, text="Python") mainloop()
Obwohl wir den Code unseres vorigen Beispielprogrammes drastisch verändert haben, sieht das grafische Ergebnis immer noch gleich aus, außer dem zusätzlichen Text "Python":
Der Vorteil unserer Codeänderungen lässt sich leicht verstehen: Wenn man beispielsweise die Höhe des Canvas auf 190, die Breite auf 90 und die ratio für das erste Rechteck auf 0.3 setzen will, so braucht man das jeweils nur an einer Stelle anzupassen, im Gegensatz zu unserer ursprünglichen Version, wo dies deutlich größere Eingriffe erforderte. Das Ergebnis unseres Beispielskriptes sieht wie folgt aus:
Ovale Objekte
Der Begriff Oval ist vom lateinischen Wort ovum, was Ei bedeutet, abgeleitet. Der Name wurde gewählt, weil die Figur eines Ovals einem Ei gleicht. Ein Oval bezeichnet eine evene rundliche und konvexe Figur. Kreise und Ellipsen sind Spezialfälle eines Ovals. Im Gegensatz zu Kreis und Ellipse muss ein beliebiges Oval keine Symmetrieachse besitzen. Die Verwendung des Begriffes Oval ist nicht immer eindeutig.Aber allen Definitionen ist folgendes gemeinsam:
- Ovale sind differenzierbare, einfache (d.h. Kurve schneidet sich nicht selbst), konvexe, geschlossene und ebene Kurven
- Sie sind sehr ähnlich zu Ellipsen
Wir können ein Oval in einem Canvas c mit der folgenden Methode kreieren:
id = C.create_oval ( x0, y0, x1, y1, option, ... )Diese Methode liefert eine Objekt-ID auf das neue ovale Objekt im Canvas C zurück.
Das folgende Programm zeichnet einen Kreis mit dem Radius 25 um den Punkt (75,75):
from tkinter import * canvas_width = 190 canvas_height =150 master = Tk() w = Canvas(master, width=canvas_width, height=canvas_height) w.pack() w.create_oval(50,50,100,100) mainloop()
Wir können auch eine kleine Funkton definieren, die unter Benutzung der Methode create_canvas() Kreise um einen Punkt (x,y) mit Radius r zeichnet.
def circle(canvas,x,y, r): id = canvas.create_oval(x-r,y-r,x+r,y+r) return id
Interaktiv in ein Canvas zeichnen
Wir können eine Applikation schreiben, mit der der Benutzer der Applikation interaktiv in ein Canvas zeichnen kann. Leider gibt es keine Möglichkeit nur einen Punkt in ein Canvas zu zeichnen. Aber wir können dieses Problem umgehen, indem wir ein kleines Oval benutzen:from tkinter import * canvas_width = 500 canvas_height = 150 def paint( event ): python_green = "#476042" x1, y1 = ( event.x - 1 ), ( event.y - 1 ) x2, y2 = ( event.x + 1 ), ( event.y + 1 ) w.create_oval( x1, y1, x2, y2, fill = python_green ) master = Tk() master.title( "Painting using Ovals" ) w = Canvas(master, width=canvas_width, height=canvas_height) w.pack(expand = YES, fill = BOTH) w.bind( "", paint ) message = Label( master, text = "Press and Drag the mouse to draw" ) message.pack( side = BOTTOM ) mainloop()
Polygone zeichnen
Um ein Polygon (Vielecke) zu zeichnen, muss man wenigsten drei Koordinaten in der Methode create_polygon() angeben:create_polygon(x0,y0, x1,y1, x2,y2, ...)
Im folgenden Beispiel zeichnen wir mit dieser Methode ein Dreieck:
from tkinter import * canvas_width = 200 canvas_height =200 python_green = "#476042" master = Tk() w = Canvas(master, width=canvas_width, height=canvas_height) w.pack() points = [0,0,canvas_width,canvas_height/2, 0, canvas_height] w.create_polygon(points, outline=python_green, fill='yellow', width=3) mainloop()
Das schaut wie folgt aus:
Wenn Sie diesen Text lesen, kann es sein, dass schon bald wieder Weihnachten sein wird. Wir liefern im folgenden ein kleines Python-Programm mit Tkinter, um das nächste Weihnachtsfest mit Weihnachtssternen aufzupeppen. Zuerst zeichnen wir einen kleinen Stern, der kaum Programmieraufwand bedeutet:
from tkinter import * canvas_width = 200 canvas_height =200 python_green = "#476042" master = Tk() w = Canvas(master, width=canvas_width, height=canvas_height) w.pack() points = [100, 140, 110, 110, 140, 100, 110, 90, 100, 60, 90, 90, 60, 100, 90, 110] w.create_polygon(points, outline=python_green, fill='yellow', width=3) mainloop()
Wie wir bereits erwähnt haben ist dieser Ansatz sehr ungeschickt. Wie sieht es beispielsweise aus, wenn wir die Größe oder die Dicke des Sterns ändern wollen? Wir müssen dann alle Punkte manuell ändern, was natürlich sehr mühsam und fehleranfällig ist. Deshalb präsentieren wir eine neue Version des vorigen Skriptes, die etwas mehr Programmierung und Programmiererfahrung erfordert. Zunächst einmal packen wir die Definition eines Sternes in eine Funktion und benutzen einen Punkt, der das Zentrum des Sternes bezeichnet, so wie weit Längen p und t zur Erzeugung eines Sternes:
Unser neues verbessertes Programm sieht nun wie folgt aus:
from tkinter import * canvas_width = 400 canvas_height =400 python_green = "#476042" def polygon_star(canvas, x,y,p,t, outline=python_green, fill='yellow', width = 1): points = [] for i in (1,-1): points.extend((x, y + i*p)) points.extend((x + i*t, y + i*t)) points.extend((x + i*p, y)) points.extend((x + i*t, y - i * t)) print(points) canvas.create_polygon(points, outline=outline, fill=fill, width=width) master = Tk() w = Canvas(master, width=canvas_width, height=canvas_height) w.pack() p = 50 t = 15 nsteps = 10 step_x = int(canvas_width / nsteps) step_y = int(canvas_height / nsteps) for i in range(1, nsteps): polygon_star(w,i*step_x,i*step_y,p,t,outline='red',fill='gold', width=3) polygon_star(w,i*step_x,canvas_height - i*step_y,p,t,outline='red',fill='gold', width=3) mainloop()
Das Ergebnis sieht nun sogar noch mehr nach Weihnachten aus und wir sind sicher, dass niemand daran zweifelt, dass es eine höllische Aufgabe wäre, jeden der Polygonpunkte direkt anzugeben, wie wir es in unserem ersten Programm gemacht hatten:
Bitmaps
Die Methode create_bitmap() kann dazu benutzt werden eine Bitmap in einem Canvas einzubetten. Die folgenden Bitmaps stehen auf allen Plattformen zur Verfügungen:"error", "gray75", "gray50", "gray25", "gray12", "hourglass", "info", "questhead", "question", "warning"
Das folgende Skript gibt alle diese Bitmaps in einem Canvas aus:
from tkinter import * canvas_width = 300 canvas_height =80 master = Tk() canvas = Canvas(master, width=canvas_width, height=canvas_height) canvas.pack() bitmaps = ["error", "gray75", "gray50", "gray25", "gray12", "hourglass", "info", "questhead", "question", "warning"] nsteps = len(bitmaps) step_x = int(canvas_width / nsteps) for i in range(0, nsteps): canvas.create_bitmap((i+1)*step_x - step_x/2,50, bitmap=bitmaps[i]) mainloop()
Das Ergebnis sieht wie folgt aus:
Bilder in einem Canvas
Mit der Methode create_image(x0,y0, options ...) kann man ein Bild in einem Canvas ausgeben. create_image akzeptiert aber kein Bild direkt. Diese Methode benötigt ein Objekt, was von der Methode PhotoImage() zurück geliefert wird. Die PhotoImage-Klasse kann aber nur GIF und PGM/PPM Bilder verarbeiten.from tkinter import * canvas_width = 300 canvas_height =300 master = Tk() canvas = Canvas(master, width=canvas_width, height=canvas_height) canvas.pack() img = PhotoImage(file="rocks.ppm") canvas.create_image(20,20, anchor=NW, image=img) mainloop()
Das Fenster, das vom vorigen Python-Programm erzeugt wird, sieht wie folgt aus:
Übung
Schreiben Sie eine Funktion, die ein kariertes Muster in ein Canvas ausgibt. Die Funktion wird mit checkered(canvas, line_distance) aufgerufen. "canvas" bezeichnet ein Canvas-Object, in das gezeichnet wird. line_distance bezeichnet den Abstand zwischen den vertikalen und den horizontalen Linien.Lösung
from tkinter import * def checkered(canvas, line_distance): # vertical lines at an interval of "line_distance" pixel for x in range(line_distance,canvas_width,line_distance): canvas.create_line(x, 0, x, canvas_height, fill="#476042") # horizontal lines at an interval of "line_distance" pixel for y in range(line_distance,canvas_height,line_distance): canvas.create_line(0, y, canvas_width, y, fill="#476042") master = Tk() canvas_width = 200 canvas_height = 100 w = Canvas(master, width=canvas_width, height=canvas_height) w.pack() checkered(w,10) mainloop()
Das Ergebnis des vorigen Skriptes: