Grafikk

Grunnleggende tegnefunksjoner

Flere muligheter

Eksempler


Kom i gang

I dette kurset benytter vi et bibliotek for å lage grafikk som heter uib-inf100-graphics. Dette biblioteket er en forenklet utgave av et standard grafikk-rammeverk i Python som heter tkinter.

I dette kurset benytter vi versjon 0.4.0 av uib-inf100-graphics.

Før du kan installere uib-inf100-graphics, må du

  • Ha installert Python 3.10 eller nyere, og
  • Ha installert tkinter.

Tkinter følger automatisk med dersom du installerte Python med hjelp av installeren fra python.org, men fulgte ikke med hvis du installerte Python med hjelp av en pakkebehandler som f.eks. apt eller brew. Hvis du ikke har tkinter, må du installere det før du kan installere uib-inf100-graphics.

Den letteste måten å installere uib-inf100-graphics er å kopiere skripet under inn i en tom Python-fil og så kjøre den. Svar yes når du blir spurt om du vil installere pakken.

import sys
from subprocess import run

package_name = "uib-inf100-graphics"

# Sjekk at Python-versjonen er 3.10 eller nyere
if ((sys.version_info[0] != 3) or (sys.version_info[1] < 10)):
    raise Exception(f"{package_name} requires Python 3.10 or later. "
            + "Your current version is: "
            + f"{sys.version_info[0]}.{sys.version_info[1]}")

# Spør brukeren om å installere pakken
ans = input(f"\n\nType 'yes' to install {package_name}: ")
if ((ans.lower() == "yes") or (ans.lower() == "y")):
    print()

    # Oppdater pip
    cmd_pip_update = f"{sys.executable} -m pip install --upgrade pip"
    print(f"Attempting to update pip with command: {cmd_pip_update}")
    run(cmd_pip_update.split())
    print()

    # Installer pakken
    cmd_install = f"{sys.executable} -m pip install {package_name}"
    print(f"Attempting to install {package_name} with command: {cmd_install}")
    run(cmd_install.split())
else:
    print(f"Did not attempt to install {package_name} now")
print("\n")

Åpne terminalen (Windows: PowerShell). Skriv inn følgende kommando for å oppdatere pip:

python -m pip install --upgrade pip

Skriv deretter denne kommandoen for å installere uib-inf100-graphics:

python -m pip install uib-inf100-graphics

Dersom du får en feilmelding om at python-kommandoen ikke finnes, prøv å erstatte python med python3, python3.11 eller py i stedet.

For å sjekke at installasjonene var vellykket, opprett en ny Python-fil og skriv inn følgende:

from uib_inf100_graphics.simple import canvas, display

canvas.create_rectangle(100, 50, 300, 150, outline="red")
canvas.create_text(200, 100, text="Hei, grafikk!", font="Arial 20 bold")

display(canvas)

Når du kjører filen skal du se et vindu som ser omtrent slik ut:

image
Koordinatsystemet

Ulikt det vi er vant til fra matematikken på skolen, vokser y-aksen nedover istedet for oppover. Dermed er \((0, 0)\) punktet til venstre øverst på lerretet, mens punktet \((\text{width}, \text{height})\) er punktet til høyre nederst. For et lerret med bredde 400 og høyde på 200, får hjørnene koordinatene under:

Koordinater for hjørnene til et lerret på 400x200

I eksempelet under har vi tegnet noen punkter på lerretet for å illustrere koordinatsystemet. Vindusstørrelsen ved bruk av uib_inf100_graphics.simple er 400x400 som standard.

Koordinatsystemet

from uib_inf100_graphics.simple import canvas, display

# polygon defined by four (x, y) coordinates (the corners)
canvas.create_polygon(200, 50,
                      350, 200,
                      200, 350,
                      50, 200,
                      outline="gray",
                      fill="")

# draw labels on the same points as corners of polygon
canvas.create_text(200, 50, text="(200, 50)")
canvas.create_text(350, 200, text="(350, 200)")
canvas.create_text(200, 350, text="(200, 350)")
canvas.create_text(50, 200, text="(50, 200)")

# x-label with arrow
canvas.create_text(180, 20, text="x", anchor="se")
canvas.create_line(180, 20, 185, 40, arrow="last")

# y-label with arrow
canvas.create_text(220, 20, text="y", anchor="sw")
canvas.create_line(220, 20, 215, 40, arrow="last")

display(canvas)


create_rectangle

Påkrevde parametre (x1, y1, x2, y2).

Valgfrie parametre (outline, fill, width, …).

from uib_inf100_graphics.simple import canvas, display

canvas.create_rectangle(10, 20, 390, 90)
canvas.create_rectangle(10, 110, 390, 190, fill='lightGreen')
canvas.create_rectangle(50, 210, 390, 290, fill='#eeaabb',
                        outline="red", width=3)
canvas.create_rectangle(10, 310, 390, 390, fill='#00308f', width=0)

display(canvas)
create_rectangle
create_oval

Påkrevde parametre (x1, y1, x2, y2).

Valgfrie parametre (outline, fill, width, …).

from uib_inf100_graphics.simple import canvas, display

canvas.create_oval(10, 20, 390, 90)
canvas.create_rectangle(10, 20, 390, 90)

canvas.create_oval(10, 110, 390, 190, fill='lightGreen')
canvas.create_oval(50, 210, 390, 290, fill='#eeaabb', outline="red", width=3)
canvas.create_oval(10, 310, 390, 390, fill='#00308f', width=0)

display(canvas)
create_oval
create_line

Påkrevde parametre (x1, y1, x2, y2, … ).

Valgfrie parametre (fill, width, arrow, smooth, …).

from uib_inf100_graphics.simple import canvas, display

canvas.create_line(10, 20, 390, 90)
canvas.create_line(10, 110, 390, 190, fill='blue', width=2, arrow='last')
canvas.create_line(10, 210, 390, 290, 50, 290, 390, 210, fill='red')

points = [(10, 310), (390, 390), (50, 390), (390, 310)]
canvas.create_line(points, fill='green', width=5, smooth=True)

display(canvas)
create_line
create_polygon

Påkrevde parametre (x1, y1, x2, y2, x3, y3, … ).

Valgfrie parametre (fill, outline, width, smooth, …).

from uib_inf100_graphics.simple import canvas, display

canvas.create_polygon(10, 20, 390, 90, 10, 90)
canvas.create_polygon(10, 110, 390, 190, 10, 190,
                      fill='', outline='red', width=3) # fill='' -> no fill

points = [50, 210, 390, 290, 50, 290, 390, 210]
canvas.create_polygon(points, fill='lightGreen', outline='black', width=1)

points = [(10, 310), (390, 390), (50, 390), (390, 310)]
canvas.create_polygon(points, fill='darkblue', smooth=True)

display(canvas)
create_polygon
create_arc

Påkrevde parametre (x1, y1, x2, y2).

Valgfrie parametre (start, extent, fill, outline, width, style, …).

from uib_inf100_graphics.simple import canvas, display

canvas.create_arc(10, 20, 390, 90, extent=270)
canvas.create_arc(10, 110, 390, 190, start=45, extent=270, fill='lightGreen')
canvas.create_arc(50, 210, 390, 290, start=45, extent=270, style='chord',
                  fill='#eeaabb', outline='red', width=8)
canvas.create_arc(10, 310, 390, 390, start=45, extent=270, style='arc')

display(canvas)
create_arc
create_text

Påkrevde parametre (x, y).

Valgfrie parametre (text, anchor, font, fill, angle, width, justify, …).

Det er flere gyldige formater å angi fonter på. Eksempler:


'TkFixedFont' er et eksempel på en navngitt font.

  • Avhengig av hvilket operativsystem du er på, kan den samme navngitte fonten se forskjellig ut. På Windows er for eksempel ‘TkFixedFont’ en font som heter Courier, mens på Mac er den en font som heter Monaco.
  • Disse navngitte fontene er garantert tilgjengelig: ‘TkDefaultFont’, ‘TkTextFont’, ‘TkFixedFont’, ‘TkMenuFont’, ‘TkHeadingFont’, ‘TkCaptionFont’, ‘TkSmallCaptionFont’, og ‘TkIconFont’.
  • Avhengig av operativsystem kan det finnes flere navngitte fonter. For å finne ut hvilke navngitte fonter som er tilgjengelige på ditt operativsystem, kan du kjøre følgende kode:
from tkinter import Tk, font

Tk().withdraw()
print(font.names())
  • Standard font dersom ingenting er spesifisert eller den spesifiserte fonten ikke blir funnet er er ‘TkDefaultFont’.

('Times new roman', 12, 'italic bold') er et eksempel på en tupel som beskriver en font.

  • Denne tupelen består av tre elementer: navnet på fontfamilien, størrelsen på fonten, og en streng som beskriver hvilke attributter fonten skal ha.
  • Mulige attributter: italic, bold, underline og overstrike (kan kombineres med mellomrom). Om du ikke ønsker noen av dem, angi en tom streng ''.
  • En font spesifisert på denne måten ser lik ut på alle operativsystemer, såfremt font-familien er installert på den aktuelle maskinen.
  • Hvilke font-familier som er tilgjengelige på ulike datamaskiner varierer.
    • Disse familiene er nesten alltid tilgjengelig: ‘Helvetica’, ‘Arial’, ‘Times’, ‘Times new roman’, ‘Courier’ og ‘Courier new’.
    • Disse familiene er ofte tilgjengelige (ikke alltid på Linux): ‘Symbol’, ‘Verdana’, ‘Georgia’, ‘Comic Sans MS’, ‘Trebuchet MS’, ‘Arial Black’, ‘Impact’.
    • Andre fonter kan virke på din maskin, men ikke regn med at det virker alle andre steder.
  • For å se en komplett liste av font-familier tilgjengelig på din maskin, kan du kjøre følgende kode:
from tkinter import Tk, font

Tk().withdraw()
print(font.families())

'Arial 20' er et eksempel på en streng som beskriver en font basert på fontfamilie og størrelse. Merk at fontfamilien ikke kan inneholde mellomrom, så om du ønsker å bruke en font-familie som består av flere ord, må du bruke en font-tupel som beskrevet over.


'Arial 20 italic underline' er et annet eksempel på en streng som beskriver en fontfamilie, størrelse og attributter. Merk at fontfamilien ikke kan inneholde mellomrom, så om du har behov for det må du bruke en font-tupel som beskrevet over.

from uib_inf100_graphics.simple import canvas, display

ax, ay = 200, 50
canvas.create_oval(ax - 5, ay - 5, ax + 5, ay + 5, fill='pink', outline='')
canvas.create_text(ax, ay, text='Hello, world!')

ax, ay = 200, 100
canvas.create_oval(ax - 5, ay - 5, ax + 5, ay + 5, fill='pink', outline='')
canvas.create_text(ax, ay, text='Carpe diem!', anchor='sw')

ax, ay = 200, 150
canvas.create_oval(ax - 5, ay - 5, ax + 5, ay + 5, fill='pink', outline='')
canvas.create_text(ax, ay, text='Ay caramba!', anchor='n', font='TkFixedFont')

ax, ay = 200, 200
canvas.create_text(ax, ay, text="Don't panic!", font=('Courier new', 20, ''))

ax, ay = 200, 250
canvas.create_text(ax, ay, text='Bazinga!', font=('Times', 30, 'italic bold'))

ax, ay = 200, 300
canvas.create_oval(ax - 5, ay - 5, ax + 5, ay + 5, fill='pink', outline='')
canvas.create_text(ax, ay, text='I have a cunning plan!', fill='blue',
                   anchor='w', angle=-30)

ax, ay = 200, 350
canvas.create_oval(ax - 5, ay - 5, ax + 5, ay + 5, fill='pink', outline='')
canvas.create_text(ax, ay, text='Here it is, your moment of zen',
                   anchor='ne', justify='right', width=150)

display(canvas)
create_text
create_image

Påkrevde parametre (x, y).

Valgfrie parametre (pil_image, anchor, …).

from uib_inf100_graphics.simple import canvas, display
from uib_inf100_graphics.helpers import load_image_http, scaled_image

# Image credits: unsplash.com/@tranmautritam
image = load_image_http('https://tinyurl.com/inf100kitten-png')

canvas.create_image(180, 180, pil_image=image)
canvas.create_oval(180 - 3, 180 - 3, 180 + 3, 180 + 3, fill='red', outline='')

smaller_image = scaled_image(image, 0.4)
canvas.create_image(250, 180, pil_image=smaller_image, anchor='nw')
canvas.create_oval(250 - 3, 180 - 3, 250 + 3, 180 + 3, fill='red', outline='')

display(canvas)
create_image

Farger

Et par farger er innebygget, som demonstrert i eksemplene over: 'black' 'white' 'gray' 'red' 'green' 'blue' 'lightGreen' 'rosyBrown', samt en hel del andre spenstige farger som du finner i dokumentasjonen til tkinter. Vi er imidlertid ikke begrenset til kun disse fargene.

Piksel

I en LED-skjerm (som er en vanlig dataskjerm) tegnes bildet på skjermen ved at hver enkelt piksel (liten prikk på skjermen) får en bestemt farge. Inne i selve skjermen sitter det tre lamper inne i hver piksel: en rød lampe, en grønn lampe og en blå lampe. Når alle tre lampene lyser med maksimal intensitet, ser vi hvitt lys komme ut av pikselen. Dersom ingen av lampene lyser, er pikselen svart. Alle fargene skjermen kan produsere, blir laget av en kombinasjon av lysintensiteter i de tre pikslene.

Hvis man zoomer inn svært tett på dataskjermen, kan man skimte at en hvit piksel ikke faktisk er helt hvit, men består egentlig av en rød, grønn og blå lampe ved siden av hverandre som lyser. Her er et bilde jeg har tatt av musenpekeren min på skjermen:

Bilde av musepekeren

Om vi zoomer litt inn på bildet, kan vi skimte at hver piksel består av tre lamper: en rød, en grønn og en blå.

Bilde av musepekeren

Fordi menneskets øye bare er i stand til å registrere lyssignaler på rød, grønn og blå frekvens, vil en blanding av røde, grønne og blå lyssignaler være tilstrekkelig for å simulere alle oppfattelser av farge et menneskeøye kan gi. Når menneskeøyet får utslag på alle tre fargekanalene, vil vi oppfatte det som hvitt lys; selv om det egentlig bare er en blanding av rødt, grønt og blått lys, og strengt tatt ikke er en blanding av alle mulige slags lysfrekvenser («ekte» hvitt lys).

Når man kjøper en LED-skjerm på butikken, finnes det ulike fargedypder eller man får oppgitt antall farger skjermen kan vise. Denne spesifikasjonen bestemmes av i hvor mange «trinn» man kan justere intensiteten til hver av de fargede lampene i en piksel. Det har lenge vært vanlig at man bruker 256 slike trinn. En farge i dette systemet kan derfor sees på som tre tall (r, g, b), der hver av r, g og b er et tall mellom 0 og 255.

Selv om nyere og dyre skjermer teknisk sett kan ha flere trinn, bruker som regel software som ikke er rettet spesielt mot high-end bildebehandling fremdeles dette systemet som standard.

Alle farger har en RGB-verdi. I tabellen ser vi at hver farge har en gitt styrke av rød (R), grønn (G) og blå (B), som er et tall mellom 0 og 255. Denne RGB-verdien kan også skrives i heksadesimalt format (se kolonnen Hex), hvor de to første tegnene etter hashtag reprsenterer styrken på rød, de to neste representerer grønn, og de to siste representerer blå sin styrke.

Farge R G B Hex Kallenavn
 
0 0 0 #000000 black
 
255 0 0 #ff0000 red
 
0 255 0 #00ff00 green1
 
0 0 255 #0000ff blue
 
255 255 0 #ffff00 yellow
 
0 255 255 #00ffff cyan
 
255 0 255 #ff00ff magenta
 
255 255 255 #ffffff white
 
128 128 128 #808080 gray
 
128 0 0 #800000 maroon
 
255 140 0 #ff8c00 dark orange
 
224 227 206 #e0e3ce -
 
248 249 245 #f8f9f5 -

For flere farger, se for eksempel listen over farger (A-F) på Wikipedia, eller prøv RGB-kalkulatoren til w3schools.com.

Vårt rammeverk for grafikk kan tolke alle RGB-verdier skrevet i hex-format.

Tekst i boks

For enkelhets skyld har vi laget en hjelpefunksjon som kan brukes til å tegne tekst midt i et rektangel, og som også gjør teksten så stor som mulig innenfor rektangelet. Denne funksjonen heter text_in_box og må importeres fra pakken uib_inf100_graphics.helpers.

Påkrevde parametre (canvas, x1, y1, x2, y2, text).

Valgfrie parametre (font, fit_mode, padding, min_font_size, justify, align, fill, …).

from uib_inf100_graphics.simple import canvas, display
from uib_inf100_graphics.helpers import text_in_box

text = "Hello, world!"

# First example
canvas.create_rectangle(100, 20, 300, 70)
text_in_box(canvas, 100, 20, 300, 70, text)

# Named font and padding.
canvas.create_rectangle(100, 120, 300, 170)
text_in_box(canvas, 100, 120, 300, 170, text,
            font="TkFixedFont",
            justify="left",
            padding=15)

# System installed font family name, fill color and fit_mode.
canvas.create_rectangle(100, 200, 300, 270)
text_in_box(canvas, 100, 200, 300, 270, text,
            font=("Times new roman", 1, ""),
            fit_mode='height', # fit_mode='height' ignores width of rectangle
            fill="blue")

# Multiline text, font style, justification.
multiline_text = text+"\n"+text+" "+text+"\n"+text
canvas.create_rectangle(100, 320, 300, 370)
text_in_box(canvas, 100, 320, 300, 370, multiline_text,
            font="Arial 42 bold italic overstrike underline",
            justify="right", # justify is 'left', 'center' or 'right'
            padding=5)

display(canvas)
text_in_box
Bilde i boks

For enkelhets skyld har vi laget en hjelpefunksjon som kan brukes til å tegne et bilde midt i et rektangel, og som også skalerer bildet slik at det passer innenfor rektangelet. Denne funksjonen heter image_in_box og må importeres fra pakken uib_inf100_graphics.helpers.

Påkrevde parametre (canvas, x1, y1, x2, y2, pil_image).

Valgfrie parametre (fit_mode, antialias).

from uib_inf100_graphics.simple import canvas, display
from uib_inf100_graphics.helpers import load_image_http, image_in_box

# Image credits: unsplash.com/@tranmautritam
image = load_image_http('https://tinyurl.com/inf100kitten-png')

image_in_box(canvas, 20, 40, 180, 180, image)
canvas.create_rectangle(20, 40, 180, 180, outline='red', width=2)
canvas.create_text(100, 35, text="fit_mode='contain'", anchor='s')

image_in_box(canvas, 20, 220, 180, 360, image, fit_mode='crop')
canvas.create_rectangle(20, 220, 180, 360, outline='red', width=2)
canvas.create_text(100, 215, text="fit_mode='crop'", anchor='s')

image_in_box(canvas, 220, 40, 380, 180, image, fit_mode='stretch')
canvas.create_rectangle(220, 40, 380, 180, outline='red', width=2)
canvas.create_text(300, 35, text="fit_mode='stretch'", anchor='s')

image_in_box(canvas, 220, 220, 380, 360, image, fit_mode='fill')
canvas.create_rectangle(220, 220, 380, 360, outline='red', width=2)
canvas.create_text(300, 215, text="fit_mode='fill'", anchor='s')

display(canvas)
image_in_box
Eksempel: buss

I videoen vises litt av tankeprosessen for å tegne en enkel buss. Her er koden som blir skrevet:

from uib_inf100_graphics.simple import canvas, display

# Body
canvas.create_rectangle(100, 100, 300, 200, fill="yellow")

# Windows
canvas.create_rectangle(100, 110, 150, 160, fill="white")
canvas.create_rectangle(260, 110, 290, 140, fill="white")
canvas.create_rectangle(220, 110, 250, 140, fill="white")

# Door
canvas.create_rectangle(160, 130, 210, 200, fill="white")
canvas.create_line(185, 130, 185, 200)

# Wheels
canvas.create_oval(120, 190, 140, 210, fill="black")
canvas.create_oval(260, 190, 280, 210, fill="black")

display(canvas)
bus