Grafikk 1
- Forkrav
- Installasjon
- Test at det virker
- Et blankt vindu
- Koordinatsystemet
- Tegn en strek
- Tegn et hyperrektangel
- Parametre for tegninger
- Tegn andre figurer og tekst
- Farger
Forkrav
Sjekk at du har tkinter installert. Dersom du installerte Python som beskrevet på installasjon-siden, skal dette allerede være i orden. Du kan sjekke om tkinter er installert ved å kopiere og kjøre dette programmet lokalt på din maskin:
import tkinter
print(tkinter.TkVersion)
Dersom du får en utskrift (f. eks 8.6), er tkinter installert. Dersom du får ModuleNotFoundError: No module named 'tkinter'
eller lignende krasj må du installere tkinter. Det enkleste er da å installere Python på nytt slik som beskrevet på
installasjon-siden. Alternativt kan tkinter installeres med ‘pip’ (se guide for å installere moduler i neste steg).
Rammeverket vi skal bruke er basert på tkinter, men i INF100 skal vi kun benytte oss av en svært liten del dette rammeverket, som kalles for Canvas. Derfor kan det å oppsøke dokumentasjonen til tkinter være mer forvirrende enn helpsomt. Om du likevel ønsker å lese dokumentasjonen, anbefaler vi denne skrevet av John W. Shipman ved New Mexico Tech.
Installasjon
- Last ned uib_inf100_graphics.py og legg den i samme mappe som programmene du arbeider med.
For å benytte alle mulighetene som finnes i vårt grafikk-bibliotek, trenger du også å installere et par moduler. Det er ikke strengt tatt nødvendig å installere dem for å jobbe med biblioteket vårt, men noen muligheter vil forsvinne (f. eks. å vise frem bilder), og du vil få meldinger som dette når programmet ditt starter:
**********************************************************
** Cannot import PIL -- it seems you need to install pillow
** This may result in limited functionality or even a runtime error.
**********************************************************
Her er stegene for å installere modulene:
- Kopier koden under inn i en python-fil, og kjør programmet
import sys, os
# Dersom tkinter ikke er installert, kan du fjerne kommentar-symbolet
# fra linjene under som omhandler tkinter
# Windows
if (os.name == "nt"):
print(f"'{sys.executable}' -m pip install pillow")
print(f"'{sys.executable}' -m pip install requests")
# print(f"'{sys.executable}' -m pip install tkinter")
# Mac/Linux
elif (os.name == "posix"):
print(f"sudo '{sys.executable}' -m pip install pillow")
print(f"sudo '{sys.executable}' -m pip install requests")
# print(f"sudo '{sys.executable}' -m pip install tkinter")
else:
print("Ukjent operativsystem")
Du vil få en utskrift, men du har ikke installert modulene enda; du har bare fått vite hvordan de skal installeres. For å installere modulene, kopiér én og én linje fra utskriften og kjør den i terminalen. For Mac er det ofte mulig å bruke terminalen i VSCode til dette. Dersom du bruker Windows eller det resulterer i feilmeldinger, kan du kjøre kommandoene i operativsystemets terminal:
Windows: Bruk PowerShell eller Command Prompt. Du kan søke etter denne fra startmenyen. Når du åpner, høyreklikk på PowerShell eller Command Prompt -ikonet og velg “run as administrator.” Kjør så kommandoene. Hvis det krasjer i PowerShell kan du også prøve å legge til
&
før kommandoen.Mac: Bruk Terminal-applikasjonen (ligger i Applications -> Utilities, eller søk etter “Terminal” med spotlight). Kjør kommandoene der.
- Ved problemer: i noen tilfeller har vi sett at installasjonen gjøres med pip for Python 2, og ikke for Python 3. For å sjekke dette, prøv å kjøre kommando
pip --version
og se hvilken python-versjon som nevnes på slutten. Hvis det er en Python 2-versjon, kjør førstpip uninstall pillow
og prøv deretterpip3 install pillow
. Evt. ta kontakt med en gruppeleder eller spør om teknisk hjelp på discord.
- Ved problemer: i noen tilfeller har vi sett at installasjonen gjøres med pip for Python 2, og ikke for Python 3. For å sjekke dette, prøv å kjøre kommando
Dersom du blir bedt om passord underveis, benytt passordet du bruker til datamaskinen.
Test at det virker
- Last ned hello_graphics.py og legg den i samme mappe som uib_inf100_graphics.py.
- Åpne mappen hvor hello_graphics.py ligger i VSCode, og kjør hello_graphics.py -filen.
Et vindu skal åpne seg og vise teksten Hello, Graphics! sammen med en kjent figur.
Et blankt vindu
from uib_inf100_graphics import *
def redraw_all(app, canvas):
# kode som tegner noe skal plasseres i denne blokken
# `pass` er en kommando som gjør ingenting; en midlertidig plassholder.
pass # erstatt denne linjen
run_app(width=400, height=200)
Vil åpne et blankt vindu (nøyaktig utseende på rammen vil variere med operativsystem):
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:
Tegn en strek
from uib_inf100_graphics import *
def redraw_all(app, canvas):
# create_line(x0, y0, x1, y1, fill='black')
# tegner en svart strek fra (x0, y0) to (x1, y1)
canvas.create_line(25, 50, 200, 100, fill='black')
run_app(width=400, height=200)
Lek litt med hvilke argumenter du gir til create_line
for å gjøre deg kjent med koordinatsystemet. Tegn mer enn en strek. Kan du lage en fin strekfigur?
Tegn et hyperrektangel
from uib_inf100_graphics import *
def redraw_all(app, canvas):
# De fire første parameterne x_0, y_0, x_1 og y_1 representerer to
# motstående hjørner i hyperrektangelet. Tenk på det som hjørnet til
# venstre øverst etterfulgt av hjørnet til høyre nederst.
canvas.create_rectangle(100, 50, app.width/2, app.height/2, fill='blue')
run_app(width=400, height=200)
Forsøk å endre på størrelsen på vinduet. Forandrer figuren seg? Hvorfor? Tegn en figur med både rektangler og streker. Klarer du å tegne en figur som endrer størrelse når brukeren endrer størrelse på vinduet?
Parametre for tegninger
from uib_inf100_graphics import *
def redraw_all(app, canvas):
# De fleste tegne-funksjoner lar oss benytte valgfrie parametre
# for å endre på tegningens utseende. Disse er skrevet på formen
# parameter_navn=parameter_verdi, og kommer etter de posisjonelle
# parameterne (som regel koordinater)
# fill endrer fargen inni figuren
canvas.create_rectangle( 0, 0, 150, 150, fill='yellow')
# width endrer tykkelsen på kanten
canvas.create_rectangle(100, 50, 250, 100, fill='orange',
outline='black', width=5)
# outline endrer fargen til kanten
canvas.create_rectangle( 50, 100, 150, 200, fill='green',
outline='red', width=3)
# width=0 fjerner kanten fullstendig
canvas.create_rectangle(125, 25, 175, 190, fill='purple', width=0)
run_app(width=400, height=200)
Tegn andre figurer og tekst
from uib_inf100_graphics import *
def redraw_all(app, canvas):
# For å tegne ovaler, oppgir vi koordinatene til hyperrektangelet som
# omgir ovalen
canvas.create_oval(100, 50, 300, 150, fill='yellow', outline='black')
# For polygoner og linjer oppgir vi en rekke med (x, y) -koordinater
# Linjer må ha to punkter
canvas.create_line(100, 50, 150, 150, fill='red', width=5)
# Polygoner må ha 3 eller flere punkter
canvas.create_polygon(100, 30, 200, 50, 300, 30, 200, 10,
fill='green', width = 5, outline='black')
# For å skrive tekst, oppgir vi en enkelt koordinat som representerer
# 'ankeret' til teksten. Vi må også ha selve teksten, og vi kan oppgi
# font/skriftstørrelse om vi ønsker
canvas.create_text(200, 100, text='INF100!',
fill='purple', font='Helvetica 26 bold underline')
# Vi kan endre hva slags betydning anker-koordinatet har.
# Prøv å sette ankeret til en av disse strengene:
# 'n' 's' 'e' 'w' 'sw' 'nw' 'ne' 'se' 'center'
canvas.create_text(200, 100, text='Grip dagen!', anchor='sw',
fill='darkBlue', font='Times 28 bold italic')
# Her er en prikk som viser ankeret brukt til begge tekstene over
canvas.create_oval(200 - 3, 100 - 3, 200 + 3, 100 + 3, fill="white")
run_app(width=400, height=200)
Farger
Et par farger er innebygget, som demonstrert i eksemplene over: 'black'
'white'
'gray'
'red'
'green'
'blue'
'RosyBrown2'
, 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:
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å.
På grunn av regler beskrevet i fysikken, vil en blanding av røde, grønne og blå lyssignaler se ut som det er hvitt.
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. For eksempel:
from uib_inf100_graphics import *
def redraw_all(app, canvas):
# Fargen "Absolute Zero" har RGB -verdi #0048BA
# Fargen "Acid green" har RGB -verdi #B0BF1A
# https://en.wikipedia.org/wiki/List_of_colors:_A%E2%80%93F
canvas.create_oval(100, 50, 300, 150,
fill='#0048BA', outline='#B0BF1A', width=10)
run_app(width=400, height=200)
Når vi sier at farger er repsentert heksadesimalt, sikter vi til 16-tallsystemet (det heksadesimale tallsystemet). Det er i dette formatet vi vanligvis representerer RGB-farger. Strengen begynner med hashtag, som egentlig bare er for å formidle at resten av strengen representerer en farge representert som (tre) hexadesimale tall. Så følger det seks tegn, hvor de to første representerer intensiteten til den røde lampen, de to neste representerer intensiteten til den grønne lampen, og de to siste representerer intensiteten til den blå lampen.
Der vårt vanlige tallsystem, titallsystemet, har 10 ulike tallsymboler (0123456789
), har det heksadesimale tallsystemet 16 tallsymboler (0123456789abcdef
). I totallsystemet, også kalt det binære tallsystemet, har vi kun to tallsymboler (0
og 1
). Måten vi teller på er den samme uansett tallsystem; for å øke et tall med én, øker vi det bakerste sifferet med én – helt til det ikke er flere tallsymboler igjen å øke med. På dette tidspunktet må man øke neste posisjon i tallet med én i stedet for, og samtidig gå tilbake til 0 på énerplassen.
For å skille tallsystemene fra hverandre, bruker vi 0b
som prefiks for tall i totallsystemet, og 0x
for tall i det heksadesimale tallsystemet. Dette er også noe python forstår:
# Oppgi verdier i ulike tallsystemer
x_dec = 100
x_bin = 0b100
x_hex = 0x100
# Print skriver ut tallet i 10-tallsystemet uansett
print(x_dec) # 100
print(x_bin) # 4 -- 100 i totallsystemet (altså 0b100) er fire
print(x_hex) # 256 -- 100 i det heksadesimal (altså 0x100) er 256
print()
# Konvertere tall til streng med str(), bin() og hex()
print("x_dec i titallsystemet:", str(x_dec))
print("x_bin i binærsystemet:", bin(x_bin))
print("x_hex i heksadesimal:", hex(x_hex))
print("255 i heksadesimal:", hex(255))
print()
# Konvertere tall til streng uten prefikser med f-strenger
print(f"x_dec i titallsystemet: {x_dec}")
print(f"x_bin i binærsystemet: {x_bin:b}") # Merk :b
print(f"x_hex i heksadesimal: {x_hex:x}") # Merk :x
print(f"255 i heksadesimal: {255:x}")
La oss telle til 0x100 i heksadesimal, og sammenligne med titallsystemet og totallsystemet:
print(f"{'Decimal':>7}{'Binary':>13}{'Hex':>7}")
for x in range(0x100 + 1):
print(f"{x:>7}{bin(x):>13}{hex(x):>7}")
For å konvertere en en RGB-verdi basert på tallverdier mellom 0 og 255 til en RGB-steng basert på hex, kan du kopiere og bruke denne funksjonen:
def rgb_hexstring(r, g, b):
""" Given the integer RGB values (0-255) for a color, return its
hexadecimal RGB string repersentation."""
return f"#{r:02x}{g:02x}{b:02x}"
Ikke tenk for mye på hva :02x betyr i funksjonen over. Men for de nysgjerrige betyr det at tallet skal representeres i sin heksadesimale form (pga. x
) og skal skrives med minst to siffer med ledende 0’er hvis nødvendig (pga. 02
).
Eksempel på bruk:
from uib_inf100_graphics import *
def rgb_hexstring(r, g, b):
""" Given the integer RGB values (0-255) for a color, return its
hexadecimal RGB string repersentation."""
return f"#{r:02x}{g:02x}{b:02x}"
def mix_colors(color1, color2, fraction_color1):
""" Mixing two colors represented as tuples of rgb integer values.
The third parameter, fraction_color1, must be a floating point
number between 0 and 1 (inclusive), which represents the mixing
ratio. If the fraction is e.g. 0.25, then the mixing ratio is
25 to 75, which means that the returned color will be closer
to color2 than to color1."""
r1, g1, b1 = color1
r2, g2, b2 = color2
fraction_color2 = 1 - fraction_color1
r = round(r1 * fraction_color1 + r2 * fraction_color2)
g = round(g1 * fraction_color1 + g2 * fraction_color2)
b = round(b1 * fraction_color1 + b2 * fraction_color2)
return (r, g, b)
def redraw_all(app, canvas):
col1 = (0xFF, 0xAA, 0x1D) # Bright yellow, det samme som (255, 170, 29)
col2 = (0xB0, 0xBF, 0x1A) # Acid green, kan også skrives (176, 191, 26)
for i in range(100):
frac1 = (99 - i)/99 # Når i=0 blir frac1=1, når i=99 blir frac1=0
r, g, b = mix_colors(col1, col2, frac1)
color = rgb_hexstring(r, g, b)
x_left = app.width/100 * i
x_right = x_left + app.width/100
canvas.create_rectangle(x_left, 0, x_right, app.height,
fill=color, width=0)
run_app(width=400, height=200)