Laboratorium składa się z dwóch części:
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("Grafika"); frame.setContentPane(new DrawPanel()); frame.setSize(1000, 700); frame.setLocationRelativeTo(null); frame.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); frame.setResizable(true); frame.setVisible(true); }
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
g.drawLine(10,10,100,100);
g.setColor(Color.yellow); g.fillOval(100,101,30,30); g.setColor(Color.black); g.drawOval(100,101,30,30);
int x[]={0,120,220,360,240}; int y[]={0,320,200,330,90}; g.fillPolygon(x,y,x.length);
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 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:
Postać macierzowa
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("Ala ma kota",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("Ala ma kota",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);
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
Swing definiuje hierarchię kształtow Shape z różnymi figurami geometrycznymi: https://docs.oracle.com/javase/8/docs/api/java/awt/Shape.html
Interesowały nas będą:
Line2D oraz Rectangle2D (zwracane przez funkcje getBounds2D())
Poniższy kod definiuje klasę DemoPanel. W konstruktorze generuje 100 losowych linii oraz 100 losowych kolorów. Następnie rysuje je w funkcji paintComponent().
public class DemoPanel extends JPanel { List<Shape> shapes = new ArrayList<>(); List<Color> colors = new ArrayList<>(); DemoPanel(){ Random rand = new Random(); for(int i = 0; i<100; i++){ shapes.add(new Line2D.Double(rand.nextDouble() *500,rand.nextDouble()*500,rand.nextDouble()*500,rand.nextDouble()*500)); colors.add(new Color(rand.nextInt(255),rand.nextInt(255),rand.nextInt(255))); } for(int i = 0; i<10; i++){ System.out.println(shapes.get(i).getBounds2D()); } } protected void paintComponent(Graphics g) { super.paintComponent(g); Graphics2D g2 = (Graphics2D) g; g2.setStroke(new BasicStroke(5, CAP_ROUND,JOIN_MITER)); for(int i = 0; i<shapes.size(); i++){ g2.setColor(colors.get(i)); g2.draw(shapes.get(i)); } } }
L-system (system Lindenmayera) to zapisany w postaci formalnej gramatyki zbiór reguł umożliwiających generację grafiki. Można o nim przeczytać w Wikipedii: https://en.wikipedia.org/wiki/L-system
Obejmuje on:
Generacja słów odbywa się w kolejnych iteracjach, podczas których wszystkie symbole nieterminalne w bieżącym słowie są zastępowane następnikiem zastosowanej reguły produkcji.
Wynikowe słowo można następnie zinterpretować graficznie, np. jako instrukcje dla „żółwia” (turtle graphics), co pozwala tworzyć złożone struktury, takie jak fraktale czy modele roślin.
Interpretacja symboli (symbolami są pojedyncze znaki):
+ – zwiększenie kąta obrotu o zadany przyrost- – zwiększenie kąta obrotu o zadany przyrost[ – zapis pozycji pozycji i kąta żółwia na stosie] – odczyt pozycji pozycji i kąta żółwia ze stosuF – przesunięcie w przód o zadaną odległośćinne litery – mogą działać analogicznie, jak F lub być ignorowane jako polecenie dla żółwia (w zależności od wybranej opcji). Wtedy służą wyłącznie do przepisywania słów.
Zadeklaruj klasę LSystem z atrybutami obejmującymi aksjomat, reguły produkcji oraz parametrami kontrolującymi generację grafiki:
public class LSystem { String name="unknown"; String axiom = ""; Map<Character,String> rules = new HashMap<>(); double stepLength = 1.0; double angleIncrement = 90; int iterations = 1; boolean allLetterForward=true; public void addRule(char lhs, String rhs) { ... } public void addRule(String lhs, String rhs) { ... } /** * Rozwija (przepisuje) słowo current w kolejnych iteracjach zaczynając od axiom * * @param iterations * @return przepisane słowo */ public String rewrite(int iterations) { String current = getAxiom(); for (int i = 0; i < iterations; i++) { StringBuilder next = new StringBuilder(); for (char c : current.toCharArray()) { // zastosuj regułę dopasowaną do znaku //lub przepisz znak, jeśli nie znajdziesz reguły // ... } current = next.toString(); } return current; } public String rewrite(){ return rewrite(iterations); } String toJson(){ // Zastosuj Gson } static LSystem fromJson(String json){ // Zastosuj Gson }
Dodaj settery/ gettery dla atrybutów…
Wykorzystaj poniższy kod do generacji obiektów klasy LSystem oraz zapisu w formacie JSON. Sprawdź, jak wygląda przepisane słowo. Czy reguły produkcji są poprawnie stosowane.
LSystem ls = new LSystem(); ls.setName("Sierpinski triangles"); ls.setAxiom("F-G-G"); ls.addRule('F', "F-G+F+G-F"); ls.addRule('G', "GG"); ls.setStepLength(6); ls.setAngleIncrement(120); ls.setIterations(3); ls.setAllLetterForward(true); var ex = ls.rewrite(); System.out.println(ex); String js = ls.toJson(); new PrintStream("sierpinski-triangles.json","UTF-8").append(js);
Klasa Scene zawiera dane do wyświetlania (linie oraz ich kolory). Zawiera także metodę, która zamienia symbole słowa na ich reprezentację graficzną.
Na przykład, słowo reprezentujące trójkąty Sierpińskiego (po 3 iteracjach) zostanie zamienione na sekwencję poleceń dla żółwia (ruchy do przodu i obroty).
F-G+F+G-F-GG+F-G+F+G-F+GG-F-G+F+G-F-GGGG+F-G+F+G-F-GG+F-G+F+G-F+GG-F-G+F+G-F+GGGG-F-G+F+G-F-GG+F-G+F+G-F+GG-F-G+F+G-F-GGGGGGGG-GGGGGGGG
public class Scene { List<Line2D> lines = new ArrayList<>(); List<Color> colors = new ArrayList<>(); void addLine(double x1, double y1, double x2, double y2) { // dodaj Line2D.Double } Rectangle2D getBounds(){ // oblicz sumę (unię) wszystkich bbox dla linii return bounds; } /** * Wypełnia lines i colors wykonując polecenia dla żółwia * @param commands - rozwinięte słowo * @param step - wielkość kroku do przodu * @param angleIncrement - wielkość o którą zmienia się kat dla znaków + oraz - * @param allLettersForward - czy wszystkie litery powodują przesunięcie żółwia, czy tylko F */ void render(String commands, double step, double angleIncrement,boolean allLettersForward){ double x=0; // połozenie żółwia double y=0; // połozenie żółwia double angle=0; // kąt w radianach Stack<double[]> stack = new Stack<>(); // stos przechowujący tablice {x,y,angle} lines.clear(); for (char c : commands.toCharArray()) { // pamietaj o konwersji kąta na radiany // angle += Math.toRadians(angleIncrement); } }
Uzupełnij kod. Podczas rysowania obraz umieszczany jest centralnie na ekranie.
Kolejne etapy to:
public class DrawPanel extends JPanel { Scene scene = new Scene(); LSystem ls; public DrawPanel(LSystem ls) { this.ls=ls; setBackground(Color.BLACK); // możesz też ustawić bitmapę jako tło } @Override protected void paintComponent(Graphics g) { super.paintComponent(g); if (scene.lines.isEmpty()) { String commands = ls.rewrite(); scene.render(commands, ls.getStepLength(), ls.getAngleIncrement(),ls.isAllLetterForward()); System.out.printf("Wygenerowaneo %d linii",scene.lines.size()); } // Wycentruj i wyskaluj obraz // Przenieś układ współrzędnych na środek panelu g2.translate(getWidth() / 2.0, getHeight() / 2.0); // Rotacja - dopasowanie do przykładów g2.rotate(-Math.PI / 2); // Ustal granice sceny Rectangle2D bounds = scene.getBounds(); // Skalowanie – dopasowanie sceny do panelu z marginesem // Zamieniona wysokość i szerokość ze wzgledu na obrót int margin = 20; double scale = Math.min(...); g2.scale(scale, -scale); // Przesunięcie środka sceny do (0, 0) double cx = bounds.getCenterX(); double cy = bounds.getCenterY(); g2.translate(-cx, -cy); // Rysowanie zmieniaj kolory i rysuj linie }
Wczytaj obiekt klasy LSystem z pliku JSON. Nastepnie wyświetl go (utwórz JFrame i DrawPanel). W pasku tytułowym ramki wyświetl nazwę L-systemu.
red, yellow i mapy string→kolor.