Table of Contents
Laboratorium 10: rysujemy choinkę
Użyjemy biblioteki graficznej Swing, a dokładniej klasy potomnej JPanel
.
Umieść w klasie głównej poniższą funkcję main()
public static void main(String[] args) { // write your code here JFrame frame = new JFrame("Choinka"); frame.setContentPane(new DrawPanel()); frame.setSize(1000, 700); frame.setLocationRelativeTo(null); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); frame.setResizable(true); frame.setVisible(true); }
DrawPanel
DrawPanel()
to klasa, której na razie brakuje. Utwórz ją w projekcie i dodaj funkcję paint(Graphics g)
z przykładowym kodem wypisującym tekst.
public class DrawPanel extends JPanel { public void paintComponent(Graphics g){ g.setFont(new Font("Helvetica", Font.BOLD, 18)); g.drawString("Hello World", 20, 20); System.out.println("painting"); } }
Zaobserwuj, kiedy na konsoli wypisywane jest painting
.
Graphics
to kontekst graficzny. Obiekt przechowuje informacje o bieżących atrybutach rysowania (font, kolor) oraz pozwala wywołać funkcje umożliwiające rysowanie wektorów oraz obrazów.
Wypróbuj kilka funkcji do rysowania. Na przykład
Rysowanie linii
g.drawLine(10,10,100,100);
Rysowanie elips
g.setColor(Color.yellow); g.fillOval(100,101,30,30); g.setColor(Color.black); g.drawOval(100,101,30,30);
Rysowanie wieloboków
int x[]={0,120,220,360,240}; int y[]={0,320,200,330,90}; g.fillPolygon(x,y,x.length);
Rysowanie obrazków
Jeżeli chciałbyś wyświetlić obrazek, należy wcześniej go załadować, np. przechowywać jako atrybut klasy. (Nie należy ładować go w funkcji paint
).
Image img = Toolkit.getDefaultToolkit().getImage("bird1.jpg"),
albo (ładowanie z zasobów)
BufferedImage img = ImageIO.read(getClass().getResource("/resources/bird1.jpg"));
a następnie wyświetlić w funkcji paint()
g.drawImage(img,0,0,getWidth(),getHeight(),this);
Graphics2D
Graphics2D
jest rozszerzoną wersją kontekstu graficznego pozwalającą na bardziej zaawansowane operacje.
Aby uzyskać dostęp do obiektu Graphics2D
wystarczy rzutowanie.
public void paintComponent(Graphics g){ Graphics2D g2d= (Graphics2D)g; }
Ważną cechą Graphics2D
jest możliwość transformacji (transformacji afinicznej) układu współrzędnych odpowiadających powinowactwom (~ klasy I LO), czyli:
- przesuniecia
- skalowanie
- rotacje
Postać macierzowa
- Wszystkie te operacje mogą być reprezentowane w postaci macierzy transformacji (3×3 dla 2D 4×4 dla 3D).
- Zobacz przykłady na https://en.wikipedia.org/wiki/Affine_transformation#Image_transformation
- Zastosowanie kilku operacji następujących po sobie można uzyskać mnożąc macierze, czyli np. funkcja Grapics2D.rotate() mnoży bieżącą macierz przez macierz rotacji.
- Macierz transformacji można zapisać w zmiennej lokalnej i załadować z powrotem do kontekstu graficznego.
Poniższy kod rysuje 12 linii obracając je o 30 stopni
// zachowaj macierz przekształcenia AffineTransform mat = g2d.getTransform(); // przesuń początek układu g2d.translate(100,100); // zastosuj skalowanie g2d.scale(.2,.2); // narysuj linie for(int i=0;i<12;i++){ g2d.drawLine(0,0,100,100); g2d.rotate(2*Math.PI/12); } //oddtwórz poprzednie ustawienia transformacji układu współrzędnych g2d.setTransform(mat);
Analogicznie możesz obrócić tekst podczas rysowania…
g2d.translate(200,200); // zastosuj skalowanie g2d.scale(.2,.2); g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); Font font = new Font("Serif", Font.PLAIN, 96); g2d.setFont(font); for(int i=0;i<12;i++){ g2d.drawString("Happy new year",150,0); g2d.rotate(2*Math.PI/12); }
Możesz ustawić atrybuty linii….
// zachowaj macierz przekształcenia AffineTransform mat = g2d.getTransform(); // przesuń początek układu g2d.translate(200,200); // zastosuj skalowanie g2d.scale(.2,.2); g2d.setStroke(new BasicStroke(50, CAP_ROUND,JOIN_MITER)); for(int i=0;i<12;i++){ //g2d.drawString("Happy new year",150,0); g2d.drawLine(0,0,100,100); g2d.rotate(2*Math.PI/12); } //oddtwórz poprzednie ustawienia transformacji układu współrzędnych g2d.setTransform(mat);
Możesz też użyć gradientowego wypełnienia dla wieloboków
Graphics2D g2d= (Graphics2D)g; AffineTransform mat = g2d.getTransform(); GradientPaint grad = new GradientPaint(0,0,new Color(0,255,0),0,100, new Color(0,10,0)); g2d.setPaint(grad); g2d.translate(0,50); g2d.scale(0.7,0.5); int x[]={286,286,223,0}; int y[]={0,131,89,108,}; g2d.fillPolygon(x,y,x.length); g2d.translate(670,0); g2d.scale(-1,1); g2d.fillPolygon(x,y,x.length); g2d.setTransform(mat);
Tło
Możesz ustawić tło wołając metodę setBackground()
klasy JPanel
Na przykład
DrawPanel(){ setBackground(new Color(0,0,50)); // setOpaque(true); }
Nie zapomnij na początku paintComponent()
wywołać metodę paintComponent()
nadklasy
Rysowanie choinki
W zasadzie można narysować choinkę składając ją z powtarzalnych elementów: gałęzie, ozdoby, świeczki, itp. Może być to jedna długa funkcja, w której wołany jest np. kod do rysowania odpowiednio przesuniętych i przeskalowanych gałęzi, ozdób, lampek itp.
Zamiast tego, zdefiniujemy klasy elementów składowych
Klasa bazowa (interfejs bazowy)
W zasadzie powinien nazywać sie Shape
, ale taki już jest https://docs.oracle.com/javase/8/docs/api/java/awt/Shape.html
Nazwiemy go XmasShape
public interface XmasShape { /** * Przesuwa poczatek układu w zadane miejsce, skaluje, jeśli trzeba obraca * @param g2d Graphics2D - kontekst graficzny */ void transform(Graphics2D g2d); /** * Zawiera kod, który rysuje elementy * @param g2d Graphics2D - kontekst graficzny */ void render(Graphics2D g2d); /** * Standardowa implementacja metody * @param g2d */ default void draw(Graphics2D g2d){ // Get the current transform AffineTransform saveAT = g2d.getTransform(); // Perform transformation transform(g2d); // Render render(g2d); // Restore original transform g2d.setTransform(saveAT); } }
Napisz klasę Bubble
To ma być jedna z ozdób
public class Bubble implements XmasShape { int x; int y; double scale; Color lineColor; Color fillColor; @Override public void render(Graphics2D g2d) { // ustaw kolor wypełnienia g2d.fillOval(0,0,100,100); // ustaw kolor obramowania g2d.drawOval(0,0,100,100); } @Override public void transform(Graphics2D g2d) { g2d.translate(x,y); g2d.scale(scale,scale); } }
Uwagi:
- obiekt zawsze jest rysowany tak samo (funkcja
render
) - zmienia się położenie i skala obiektu; jest ona interpretowana w funkcji
transform
Refaktoryzacja DrawPanel
Dodaj atrybut
List<XmasShape> shapes = new ArrayList<>();
Zmień funkcję paintComponent()
na następującą:
public void paintComponent(Graphics g){ super.paintComponent(g); for(XmasShape s:shapes){ s.draw((Graphics2D)g); } }
Dodaj obiekty
Dodaj kilka obiektów klasy Bubble
i sprawdź, czy wyświetlane są zgodnie z oczekiwaniami.
Napisz osobną funkcje do dodawania obiektów w DrawPanel
i wywołaj ją w konstruktorze lub funkcji main()
. Nie dodawaj obiektów w paintCpmponent()
Kolejne klasy
- Dodaj klasę
Branch
- zielona gałąź drzewa - Możesz utworzyć klasę
Tree
zawierającą listęXmasShape
i złożyć gotowe drzewko z gałęzi - Dodaj klasę
Light
- Możesz dodać gwiazdki i inne ozdoby…
Za każdym razem:
- określ, jakie atrybuty są potrzebne, aby przeprowadzić transformację układu współrzędnych
- napisz funkcję
transform()
- napisz kod rysujący element –
render()
Proszę o przesyłanie na UPEL także zrzutów ekranu choinek
Fragmenty kodu
import java.awt.*; import java.util.ArrayList; import java.util.List; import java.util.Random; public class Bucket implements XmasShape { List<XmasShape> shapes = new ArrayList<>(); int x; int y; Bucket(int x,int y){ this.x=x; this.y=y; Random r = new Random(); for(int i=0;i<10;i++){ Bubble b = new Bubble(); b.x=r.nextInt(100); b.y=r.nextInt(100); b.scale=r.nextDouble()*0.2; b.fillColor=new Color(r.nextFloat(),r.nextFloat(),r.nextFloat()); b.lineColor=new Color(r.nextFloat(),r.nextFloat(),r.nextFloat()); shapes.add(b); } } @Override public void transform(Graphics2D g2d) { g2d.translate(x,y); } @Override public void render(Graphics2D g2d) { g2d.setColor(new Color(192,192,192)); g2d.fillRect(0,0,100,100); for(var b:shapes)b.draw(g2d); } }
oraz
DrawPanel(){ shapes.add(new Bucket(10,10)); shapes.add(new Bucket(10,400)); shapes.add(new Bucket(400,10)); shapes.add(new Bucket(400,400)); }