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[]={286,253,223,200,148,119,69,45,0};
        int y[]={0,101,89,108,79,95,66,80,56};
        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"),

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 układu współrzędnych odpowiadających powinowactwom (~ klasy I LO), czyli:

  • przesuniecia
  • skalowanie
  • rotacje

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 wypenienia 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,200,148,119,69,45,0};
        int y[]={0,131,89,108,79,95,66,80,56};
        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 umieszczenie na git zrzutów ekranu choinek

po/lab10.txt · Last modified: 2017/12/21 14:00 by pszwed
CC Attribution-Share Alike 4.0 International
Driven by DokuWiki Recent changes RSS feed Valid CSS Valid XHTML 1.0