Grafikk
- Rammeverket
- Koordinatsystemet
- Grunnleggende figurer
- Hjelpemetoder
- Farger
- Strenger
- Bilder
- Tegnestil
- Adaptiv tegning
Relevant litteratur i offisiell tutorial: Getting started with graphics
I INF101 skal vi bruke Swing som rammeverk for å lage grafiske brukergrensesnitt. I forhold til alternative rammeverk man kunne valgt er Swing lett å komme i gang med, og har god støtte for ulike plattformer uten å måtte gjøre spesielle tilpasninger.
Rammeverket
For å bruke Swing som rammeverk for å lage grafikk, trenger vi to elementer: en klasse som utvider JPanel
og en main
-metode som starter programmet. Det ville virket fint å ha main
-metoden i samme klasse som utvidet JPanel, men det er bedre stil å ha dem adskilt.
import javax.swing.JPanel;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
public class MyFirstCanvas extends JPanel {
public MyFirstCanvas() {
this.setPreferredSize(new Dimension(220, 100));
}
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
// Tegner først et fyllt rosa kvadrat
Rectangle2D shape1 = new Rectangle2D.Double(100, 20, 50, 50);
g2.setColor(Color.PINK);
g2.fill(shape1);
// Tegner deretter omrisset av et svart rektangel.
// Ny tegning kommer «oppå» det som er tegnet fra før.
Rectangle2D shape2 = new Rectangle2D.Double(110, 30, 50, 30);
g2.setColor(Color.BLACK);
g2.draw(shape2);
}
}
import javax.swing.JFrame;
public class Main {
public static void main(String[] args) {
MyFirstCanvas canvas = new MyFirstCanvas();
JFrame frame = new JFrame();
frame.setTitle("INF101");
frame.setContentPane(canvas);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.pack();
frame.setVisible(true);
}
}
MyFirstCanvas canvas = new MyFirstCanvas();
Her oppretter vi et objekt av typen MyFirstCanvas. Dette objektet representerer lerretet som tegnes inne i vinduet vårt.
JFrame frame = new JFrame();
Vi oppretter et objekt i klassen JFrame. Dette objektet representerer «rammen» rundt vinduet, som inneholder tittelen og knappene for å lukke vinduet.
frame.setTitle("INF101");
Vi setter tittelen som vises på rammen.
frame.setContentPane(canvas);
Vi setter lerretet vårt inn i rammen, slik at det fra nå av er en del av rammen.
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
Vi spesifiserer at når noen lukker vinduet, skal programmet avsluttes. Hvis vi ikke hadde gjort dette, ville programmet fortsatt kjøre i bakgrunnen, selv om vinduet var lukket.
frame.pack();
Her sier vi at rammen skal justere størrelsen sin slik at den passer til lerretet sin foretrukne størrelse.
-
frame.setVisible(true);
Vi gjør rammen synlig på skjermen («henger rammet opp på veggen»).
MyFirstCanvas extends JPanel
Her integrerer vi klassen vår inn i Swing-rammeverket. JPanel er en klasse som er definert i Swing, og som representerer et lerret som kan tegnes på. Når vi utvider JPanel, arver vi alle metodene som er definert der, slik som setPreferredSize(Dimension) og paintComponent(Graphics).
this.setPreferredSize(new Dimension(220, 100));
Vi spesifiserer at lerretet «foretrekker» å ha en størrelse på 300 piksler i bredden og 150 piksler i høyden.
@Override public void paintComponent(Graphics g)
Vi overskriver metoden paintComponent(Graphics) som er definert i klassen JPanel. Denne metoden blir kalt når lerretet skal tegnes, og vi overskriver den slik at vi kan tegne vår egen tegning, og ikke bare et blankt vindu.
super.paintComponent(g);
Vi tegner bakgrunnen til lerretet. Teknisk sett kaller vi her på metoden paintComponent(Graphics) slik den er definert i klassen JPanel, som er vår super-klasse (siden vår klasse utvider JPanel). Når man overskriver en metode som allerede er implementert i klassen vi arver fra, slik vi gjør her, er det vanlig at man ønsker å legge til funksjonalitet i stedet for å endre den fullstendig. Dette gjøres her.
Graphics2D g2 = (Graphics2D) g;
Objektet g vi kommer til få som argument i denne metoden er egentlig av typen Graphics2D, som er en undertype av Graphics. På denne linjen caster vig
til typenGraphics2D
, noe som er mulig fordi objektetg
allerede har denne typen. Hensikten med å gjøre dette er at vi får tilgang til metoder som er definert i klassen Graphics2D, som gjør oss i stand til å tegne på flere måter, blant annetfill
ogdraw
-metodene vi bruker under.
Rectangle2D shape1 = new Rectangle2D.Double(100, 20, 50, 50);
Vi oppretter et rektangel-objekt med hjørne øverst til venstre i punktet (100, 20), og med bredde 50 og høyde 50. Merk at koordinatsystemet er «opp ned», slik at øverst til venstre på lerretet er punktet (0, 0) og punktet nederst til høyre er (220, 100).
g2.setColor(Color.RED);
Vi velger fargen vi skal bruke til å tegne med.
g2.fill(shape1);
Vi tegner figuren med en fylt farge.
Rectangle2D shape2 = new Rectangle2D.Double(110, 30, 50, 30);
Vi oppretter et rektangel-objekt med hjørne øverst til venstre i punktet (110, 30), og med bredde 50 og høyde 30. Merk at koordinatsystemet er «opp ned», slik at dette hjørnet til er til høyre og ned i forhold til forrige rektangel.
g2.setColor(Color.BLACK);
Vi velger fargen vi skal bruke til å tegne med.
-
g2.fill(shape2);
Vi tegner omrisset av figuren.
Når vi beskriver eksempler for å tegne figurer videre i disse kursnotatene, er viser vi kun frem koden i paintComponent som kommer etter at vi caster til Graphics2D med mindre noe annet er spesifisert.
Koordinatesystemet
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 220 og høyde på 100, får hjørnene koordinatene under:
Grunnleggende figurer
I tabellen under bruker vi varianter av denne koden for å tegne figurer:
Shape shape = new /* lim inn kodelinje fra tabellen under her */;
g2.setColor(Color.PINK);
g2.fill(shape);
g2.setColor(Color.BLACK);
g2.draw(shape);
Vi tegner altså den samme figuren to ganger, først med fyllfargen rosa, deretter med omriss i svart.
De fleste klassene i dette avsnittet må importeres fra java.awt.geom, mens
Shape
må importeres fra java.awt.
Kode | Figur |
---|---|
Parametre:
fill tegner ingenting, må tegnes med draw . |
|
Parametre:
|
|
Parametre:
|
|
Parametre:
|
|
Parametre:
|
|
Merk:
|
Hjelpemetoder
Generelt i programmering er det alltid lurt å bruke hjelpemetoder. En hjelpemetode er en metode man kaller på et annet sted for å gjøre en avgrenset del av oppgaven til koden som kaller på den.
Når vi jobber med grafikk er hjelpemetoder like viktige or nyttige som i annen programmering. Noen eksempler:
@Override
public void paintComponent(Graphics g) {
super.paintComponent(g);
Graphics2D g2 = (Graphics2D) g;
// Fire sirkler inni hverandre
fillCenteredCircle(g2, 50, 50, 30, Color.DARK_GRAY);
fillCenteredCircle(g2, 50, 50, 20, Color.GRAY);
fillCenteredCircle(g2, 50, 50, 10, Color.LIGHT_GRAY);
fillCenteredCircle(g2, 50, 50, 2, Color.WHITE);
// To strekmenn
drawMan(g2, new Rectangle2D.Double(120, 10, 30, 80));
drawMan(g2, new Rectangle2D.Double(170, 20, 30, 70));
}
/** Fill circle with radius r centered around (cx, cy) with given color */
private static void fillCenteredCircle(Graphics2D g2, double cx, double cy,
double r, Color color) {
double xLeft = cx - r;
double yTop = cy - r;
double diameter = 2*r;
Ellipse2D circle = new Ellipse2D.Double(xLeft, yTop, diameter, diameter);
g2.setColor(color);
g2.fill(circle);
}
/** Draw a stick man within the bounds of the given box */
private static void drawMan(Graphics2D g2, Rectangle2D box) {
// Selvdokumenterende kode har beskrivende variabelnavn
// i stedet for mystiske utregninger
double xLeft = box.getMinX();
double xMid = box.getCenterX();
double xRight = box.getMaxX();
double yTop = box.getMinY();
double yNeck = yTop + box.getHeight() * 0.33;
double yHands = yTop + box.getHeight() * 0.5;
double yWaist = yTop + box.getHeight() * 0.66;
double yFeet = yTop + box.getHeight();
double headHeight = yNeck - yTop;
double headWidth = box.getWidth();
// Hodet
g2.setColor(Color.YELLOW);
g2.fill(new Ellipse2D.Double(xMid - headWidth/2, yTop, headWidth, headHeight));
g2.setColor(Color.BLACK);
g2.draw(new Ellipse2D.Double(xMid - headWidth/2, yTop, headWidth, headHeight));
// Kroppen og armene
g2.draw(new Line2D.Double(xMid, yNeck, xMid, yWaist));
g2.draw(new Line2D.Double(xMid, yNeck, xLeft, yHands));
g2.draw(new Line2D.Double(xMid, yNeck, xRight, yHands));
// Beina
g2.draw(new Line2D.Double(xMid, yWaist, xLeft, yFeet));
g2.draw(new Line2D.Double(xMid, yWaist, xRight, yFeet));
}
Farger
Vi bestemmer fargen vi skal bruke for å tegne figurer ved å kalle setColor
-metoden på Graphics2D-objektet. Vi kan bruke fargenavn eller RGB-verdier for å lage fargeobjekter. Noen eksempler:
Shape rect1 = new Rectangle2D.Double(10, 10, 60, 35);
g2.setColor(Color.BLUE); // Noen få farger er innebygd med navn
g2.fill(rect1);
Shape rect2 = new Rectangle2D.Double(80, 10, 60, 35);
g2.setColor(Color.BLUE.darker()); // .darker() og .brighter() kan endre fargen
g2.fill(rect2);
Shape rect3 = new Rectangle2D.Double(150, 10, 60, 35);
g2.setColor(new Color(0x8800ff)); // RGB som hexadesimaltall
g2.fill(rect3);
Shape rect4 = new Rectangle2D.Double(150, 60, 60, 35);
g2.setColor(Color.decode("#8800ff")); // RGB som streng med hexadesimaltall
g2.fill(rect4);
Shape ball1 = new Ellipse2D.Double(10, 50, 40, 40);
g2.setColor(new Color(0, 180, 255)); // RGB (0-255 for hver av rød, grønn og blå)
g2.fill(ball1);
// Gjennomsiktig ball
Shape ball2 = new Ellipse2D.Double(30, 20, 100, 100);
g2.setColor(new Color(0, 128, 0, 80)); // RGBA (0-255) (alpha = gjennomsiktighet)
g2.fill(ball2);
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 0x
representerer 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 | 0x000000 |
BLACK |
|
255 | 0 | 0 | 0xff0000 |
RED |
|
0 | 255 | 0 | 0x00ff00 |
GREEN |
|
0 | 0 | 255 | 0x0000ff |
BLUE |
|
255 | 255 | 0 | 0xffff00 |
YELLOW |
|
0 | 255 | 255 | 0x00ffff |
CYAN |
|
255 | 0 | 255 | 0xff00ff |
MAGENTA |
|
255 | 255 | 255 | 0xffffff |
WHITE |
|
128 | 128 | 128 | 0x808080 |
GRAY |
|
255 | 171 | 173 | 0xffabad |
PINK |
|
255 | 197 | 0 | 0xffc500 |
ORANGE |
|
224 | 227 | 206 | 0xe0e3ce |
- | |
249 | 248 | 245 | 0xf9f8f5 |
- |
For flere farger, se for eksempel listen over farger (A-F) på Wikipedia, eller prøv RGB-kalkulatoren til w3schools.com.
Strenger
Vi kan skrive tekst i et vindu ved å bruke drawString
-metoden i Graphics2D-objektet. Denne metoden tar inn en streng og to tall, som er koordinatene til venstre side av «stamlinjen» man kan forestille seg går under teksten.
g2.setColor(Color.DARK_GRAY);
g2.setFont(new Font("Arial", Font.BOLD, 20));
g2.drawString("Her kan jeg skrive", 10, 30);
// Tegn prikk samme sted som koordinatet gitt til drawString
Shape marker = new Ellipse2D.Double(8, 28, 5, 5);
g2.setColor(Color.RED);
g2.fill(marker);
Fonter. Merk at hvilke fonter som er tilgjengelig kommer an på hvilket operativsystem du er på og fontene som er installert der. Fontene Monospaced
, Serif
, SansSerif
, Dialog
og DialogInput
vil derimot alltid være tilgjengelige, selv om de kan se litt forskjellig ut på ulike operativsystemer. Se også offisiell dokumentasjon for java.awt.Font og offisiell tutorial om fonter.
g2.setColor(Color.DARK_GRAY);
g2.setFont(new Font("Monospaced", Font.PLAIN, 12));
g2.drawString("Hurra", 50, 20);
g2.setColor(Color.RED);
g2.setFont(new Font("Serif", Font.BOLD, 24));
g2.drawString("Hurra", 50, 40);
g2.setColor(Color.ORANGE.darker());
g2.setFont(new Font("SansSerif", Font.BOLD|Font.ITALIC, 48));
g2.drawString("Hurra", 50, 80);
Å sentrere tekst. Vi har laget en hjelpemetode i Inf101Graphics som gjør det enkelt å sentrere tekst. Parametrene til drawCenteredString
-metoden er
- et Graphics (eller Graphics2D) -objekt å tegne på,
- en streng som skal tegnes, og
- fire double som beskriver rektangelet teksten skal sentreres i (x venstre, y øverst, bredde, høyde).
Merk at metoden ikke vil respektere størrelsen på rektangelet når teksten skrives; vi bare oppgir posisjonen på denne måten for enkelhets skyld, siden et vanlig use-case er å sentrere teksten på et rektangel (se eksempel under). Det er fullt mulig å la bredde og høyde være 0, da sentreres teksten rundt posisjonen (x, y).
For enkelhets skyld inneholder Inf101Graphics også en variant av metoden som tar et Rectangle2D -objekt som input i stedet for de fire double-verdiene, samt en variant som har to double-variabler x og y som parametre uten å ta med bredde og høyde.
g2.setColor(Color.RED);
g2.setFont(new Font("Arial", Font.BOLD, 20));
g2.drawRect(10, 30, 180, 60);
Inf101Graphics.drawCenteredString(g2, "Midtstilt tekst", 10, 30, 180, 60);
Bilder
Vi har laget et par hjelpemetoder i Inf101Graphics som gjør det enkelt å vise bilder. Vi anbefaler at bildene ligger i mappen src/main/resources i maven-prosjektet, og at du bruker loadImageFromResources
-metoden for å laste inn bildet først. Deretter kan vi bruke drawImage
-metoden for å tegne bildet på skjermen. Parametrene til drawImage
og drawCenteredImage
-metodene er
- et Graphics (eller Graphics2D) -objekt å tegne på,
- et BufferedImage-objekt som skal tegnes,
- to double (x, y) som beskriver posisjonen til bildet (øverst venstre hjørne for
drawImage
, midten fordrawCenteredImage
), - en double som beskriver skaleringen av bildet sin størrelse (
1.0
er 100% størrelse), og - (valgfritt) en double som beskriver rotasjonen av bildet (i radianer).
BufferedImage
må importeres fra java.awt.image
// Begynn med "/" før filnavnet, og la bildet ligge i "resources" -mappen
BufferedImage image = Inf101Graphics.loadImageFromResources("/inf101.png");
g2.setColor(Color.RED); // Farge til prikkene som viser (x, y) -posisjon
double x = 10;
double y = 30;
Inf101Graphics.drawImage(g2, image, x, y, 0.15);
g2.fill(new Ellipse2D.Double(x - 3, y - 3, 6, 6));
x = 90;
Inf101Graphics.drawImage(g2, image, x, y, 0.15, Math.PI/16);
g2.fill(new Ellipse2D.Double(x - 3, y - 3, 6, 6));
x = 170;
Inf101Graphics.drawCenteredImage(g2, image, x, y, 0.15, Math.PI/6);
g2.fill(new Ellipse2D.Double(x - 3, y - 3, 6, 6));
Tegnestil
På samme måte som vi kan endre hvilken farge det blir tegnet med, er det også mulig å endre på «stilen» til streker og figurer.
Linjetykkelse
// Vi tegner for eksempel en sti, men det kunne også vært noe annet
// vi skal tegne med 'draw', f. eks en linje, en oval, rektangel etc.
Path2D path = new Path2D.Double();
path.moveTo(20, 30);
path.lineTo(30, 70);
path.lineTo(180, 80);
// Linjetykkelse 20
g2.setColor(Color.GREEN.darker().darker());
g2.setStroke(new BasicStroke(20));
g2.draw(path);
// Til sammenligning: et fylt rektangel med høyde på 20 piksler
g2.fill(new Rectangle2D.Double(130, 10, 50, 20));
// Linjektykkelse 10
g2.setColor(Color.decode("#FFFF88"));
g2.setStroke(new BasicStroke(10));
g2.draw(path);
// Linjetykkelse 1
g2.setColor(Color.BLACK);
g2.setStroke(new BasicStroke(1));
g2.draw(path);
Legg merke til at tykke linjer begynner «før» punktet vi oppgir som startpunkt, og at de slutter «etter» punktet vi oppgir som sluttpunkt. Dette kan imidlertid konfigureres. Les mer om dette og mer (f. eks. hvordan ha stiplede linjer) i dokumentasjonene til BasicStroke og i offisiell tutorial om Stroking and Filling Graphics Primitives.
Gradiente farger
// I stedet for å bruke setColor, kan vi bruke setPaint
// for å sette fargen til en GradientPaint
Color color1 = Color.MAGENTA.darker();
Color color2 = Color.ORANGE;
g2.setPaint(new GradientPaint(60, 40, color1, 140, 80, color2));
// Tegner linjen hvor gradienten skjer (argumentene til GradientPaint)
g2.setColor(Color.WHITE);
g2.draw(new Line2D.Double(60, 40, 140, 80));
Les mer om dette i dokumentasjonen til GradientPaint og i offisiell tutorial om Stroking and Filling Graphics Primitives.
Se også:
- LinearGradientPaint for gradienter med flere enn to farger.
- RadialGradientPaint for runde gradienter.
- TexturePaint for å bruke et repeterende mønster som fyllfarge.
Adaptiv tegning
Selv om et lerretet har en foretrukket størrelse (se rammeverket), er det ikke alltid at lerretet faktisk vil ha denne størrelsen. Det kan være for eksempel dersom brukeren manuelt endrer størrelse på vinduet, eller dersom lerretet inngår i en større helhet hvor andre deler av programmet griper inn og endrer lerretets størrelse. Fordi slike tilfeller er svært vanlige, anbefaler vi alltid å tegne på lerretet med tanke på at det kan ha en størrelse som er forskjellig fra foretrukket størrelse.
For å gjøre tegningen adaptiv, bruker vi metodene this.getWidth()
og this.getHeight()
som gir oss henholdsvis bredde og høyde av lerretet. Så regner vi ut størrelsen og plasseringen til ting vi vil tegne basert på disse verdiene. Noen eksempler:
Plassering relativt til lerretets sentrum
// En sirkel med radius 25px som alltid er i midten av lerretet
double r = 25;
double cx = ((double) this.getWidth()) / 2;
double cy = ((double) this.getHeight()) / 2;
g2.setColor(Color.RED);
g2.fill(new Ellipse2D.Double(cx - r, cy - r, 2 * r, 2 * r));
// En strek som alltid er 10px til høyre for sirkelen
double x = cx + r + 10;
g2.setColor(Color.BLACK);
g2.draw(new Line2D.Double(x, cy - r, x, cy + r));
Størrelse relativ til lerretets størrelse
// Et sentrert rektangel med 80% av lerretets høyde og 80% av lerretets bredde
double height = this.getHeight() * 0.8;
double width = this.getWidth() * 0.8;
double x = this.getWidth() * 0.1;
double y = this.getHeight() * 0.1;
g2.setColor(Color.LIGHT_GRAY);
g2.fill(new Rectangle2D.Double(x, y, width, height));
Fast avstand til kantene på lerretet
// Et rektangel som alltid har 15px avstand til kantene på lerretet
double margin = 15;
double x = margin;
double y = margin;
double width = this.getWidth() - 2 * margin;
double height = this.getHeight() - 2 * margin;
g2.setColor(Color.GREEN.darker().darker());
g2.fill(new Rectangle2D.Double(x, y, width, height));
Rute med størrelse avhengig av tekst
Dette eksempelet benytter en hjelpemetode for å tegne midtstilte strenger hentet fra Inf101Graphics.
// En rute nede til høyre med størrelse avhengig av teksten inne i ruten
String text = "Min tekst";
// Vi lar fontstørrelsen variere med høyden på lerretet
// (ikke særlig vanlig i praksis, men for å illustrere at det er mulig)
int fontSize = this.getHeight() / 6; // 6 er bare et tilfeldig tall
g2.setFont(new Font("SansSerif", Font.PLAIN, fontSize));
// Hente informasjon om størrelsen på teksten
int textWidth = g2.getFontMetrics().stringWidth(text); // Bredde av teksten
int textHeight = g2.getFontMetrics().getHeight(); // Høyde av teksten
// Regne ut størrelsen på boksen
double textMargin = 10; // Avstand mellom teksten og ruten
double boxWidth = textWidth + 2 * textMargin;
double boxHeight = textHeight + 2 * textMargin;
// Regne ut posisjonen til boksen
double boxMargin = 20; // Avstand mellom ruten og kantene på lerretet
double boxX = this.getWidth() - boxWidth - boxMargin;
double boxY = this.getHeight() - boxHeight - boxMargin;
Rectangle2D box = new Rectangle2D.Double(boxX, boxY, boxWidth, boxHeight);
// Tegne
g2.setColor(Color.BLACK);
g2.draw(box);
Inf101Graphics.drawCenteredString(g2, text, box);