Canvas-Element


Einführung: Linien und Rechtecke

Python-Schlange im Bilderrahmen (Canvas)
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:

Gerade horizontale Linie in einem Canvas

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.

Canvas mit Rechtecken und Linien

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:

Canvas mit Rechtecken und Linien

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":

Canvas mit Text

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:

Leicht adaptierbar durch Codeänderung

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:

Eclipses in einem Canvas

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()


Painting /  Python in ein Canvas mit Mausbewegungen
zeichnen

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:

Polygon in einem Canvas

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()

Star created with create_polygon of tkinter

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:

Bezeichnungen für die Sternfunktion

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:

Sterne

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:

Bitmap on Canvas

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:

Felsen in einem Canvas, Bodensee bei Radolfzell

Ü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.

Erlätuterund der Parameter

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:

Kariertes Muster in canvas