Laboratorium 5

Prosty program do rysowania (Paint)


Zadanie główne (5 pkt)

Stwórz program pozwalający na rysowanie prostych kształtów (linia, krzywa, kwadrat…). Program powinien mieć możliwość zmiany koloru i grubości konturu rysowanego kształtu i przechowywać wszystkie narysowane kształty na obszarze rysowania. Program powinien także posiadać opcję wczytania obrazu z pliku, zapisu obszaru rysowania do pliku i czyszczenia obszaru rysowania.

Wymagania techniczne:

  1. Umiejętne korzystanie z wybranych layoutów (komponenty znajdują się dokładnie tam, gdzie tego chce twórca, a nie „jak layout układa”).
  2. Wykorzystanie odpowiednich komponentów JSwing do odpowiedniego zadania (np. wybór koloru przez JColorChooser).
  3. Obsługa zdarzeń powinna być intuicyjna – jeżeli klient chce zmienić narzędzie rysowania, powinien to móc zrobić jednym kliknięciem.

Punktacja:

  1. Stworzenie okna aplikacji (odpowiedni Layout, wszystkie komponenty JSwing) – 1 pkt
  2. Rysowanie – 2 pkt
    1. Przynajmniej 2 różne narzędzia rysowania (gumka, linia, krzywa, pusty/pełny kwadrat…), przy czym jednym z nich musi być tzw. „ołówek”
    2. Krzywe i figury rysują się „za ruchem myszy”
    3. Możliwość wyczyszczenia obszaru rysowania
  3. Zapis obszaru rysowania do pliku i możliwość dodania obrazu z pliku (lub adresu URL) do obszaru rysowania, realizowany przez pasek menu (JMenu) – 1 pkt
  4. Zmiana koloru i grubości konturu rysowanego kształtu – 1 pkt

To zadanie możesz wykonać tak, jak do tej pory – spełniając kolejne wymagania postawione w sekcji Punktacja, albo przejść przez kolejne kamienie milowe w poniższym scenariuszu. Każdy kolejny krok dodaje nową funkcjonalność, ale robi to stopniowo, dzięki czemu nie powinieneś pogubić się w pisaniu programu.

Scenariusz:

  1. Stwórz okno (JFrame) zawierające panel (JPanel), w którym po naciśnięciu myszy (wykorzystaj MouseListener), w miejscu kliknięcia, będzie rysowany pewien konkretny kształt (np. kwadrat o określonej długości boku).

  2. Dodaj i oprogramuj elementy pozwalające na zmianę koloru i grubości linii rysowanego kształtu. Kolor powinien być wybierany za pomocą komponentu JColorChooser.

  3. Zadbaj o to, aby kolejne kliknięcia myszy nie usuwały poprzednio narysowanych kształtów. (N kliknięć= N kształtów w panelu). Na tym etapie upewnij się też, że wybierając kolor, nie zmieniasz koloru linii narysowanych już elementów.

  4. Zmodyfikuj rysowany kształt tak, aby jego wielkość i kształt dało się ustalić za pomocą zdarzeń z myszy. Dodaj drugie narzędzie – ołówek. Zadbaj o to, aby oba kształty były rysowane poprawnie.

  5. Stwórz menu (JMenuBar), dzięki któremu będzie można

    1. zapisać zawartość panelu do pliku graficznego,

    2. wczytać dowolną grafikę,

    3. wyczyścić panel rysowania.

Za osiągnięcie każdego kamienia milowego otrzymasz 1 punkt.

W obu przypadkach możesz otrzymać dodatkowe punkty za „nadprogramowe” narzędzia – inne figury, gumka… . Za każde dodatkowe narzędzie otrzymasz 0,5 punktu. Narzędzia muszą spełniać założenia z podpunktu 2b.

Dobrym ćwiczeniem będzie otwieranie i zapis plików za pomocą JFileChooser, ale nie jest to konieczne do ukończenia tego zadania.

Wskazówki:

public class Linia {

       private List<Integer> xList;  // Lista współrzędnych x 
       private List<Integer> yList;  // Lista współrzędnych y
     
       // Konstruktor
       public Linia() {
          xList = new ArrayList<Integer>();
          yList = new ArrayList<Integer>();
       }
     
       // Dodawanie punktów do linii
       public void addPoint(int x, int y) {
          xList.add(x);
          yList.add(y);
       }
     
       // Metoda pozwalająca linii na rysowanie siebie samej, jeżeli będzie miała dostęp do Graphics2D/Graphics
       public void draw(Graphics2D g2d) { 
          for (int i = 0; i < xList.size() - 1; ++i) 
          {
             g2d.drawLine(xList.get(i), yList.get(i), xList.get(i + 1), yList.get(i + 1));
          }
       }

}

Analogiczną klasę możemy stworzyć dla dowolnego rysowanego kształtu.

 

Proponujemy dwa sposoby podejścia do problemu przechowywania kształtów:

1. Zapamiętywanie parametrów każdego kształtu osobno.
W panelu będącym obszarem rysowania możemy stworzyć listy przechowujące wszystkie narysowane kształty:

ArrayList<Linia> lines= new ArrayList<Linia>();

które potem należy rysować w metodzie paintComponent:

super.paintComponent(g);
Graphics2D g2d = (Graphics2D) g;
for (linia line: lines) line.draw(g2d);
  • Dodajemy kształty do odpowiednich list w listenerach (mousePressed, mouseDragged). Należy pamiętać o repaint()!
  • To podejście umożliwia rozszerzenie funkcjonalności programu o modyfikację już istniejących kształtów.
     

2. Przechowywania wszystkich informacji w samym obrazku.

W panelu będącym obszarem rysowania możemy stworzyć obiekt BufferedImage, np:

BufferedImage image;
który będzie modyfikowany w odpowiednich metodach (zdarzenie kliknięcia myszą, puszczenie przycisku, itd), np:
Graphics2D g2 = image.createGraphics();
g2.fillRect(e.getX(), e.getY(), 2, 2);
repaint();
i rysowany jako całość w paintComponent.
public void paintComponent(Graphics g) {
  if(image==null){
    image =  (BufferedImage)this.createImage(400, 400);
    Graphics2D gc = image.createGraphics();
    gc.setColor(Color.white);
    gc.fillRect(0, 0, 400, 400);
  }
	        
  Graphics2D g2d = (Graphics2D) g;
  g2d.drawImage(image, 0, 0, this);
}

 

Odczyt i zapis obrazków do pliku.

Zapis do pliku możesz wykonać w taki prosty sposób:

//Tworzenie obiektu BufferedImage, w którym znajdzie się nasza grafika z panelu paintPanel 
BufferedImage image = new BufferedImage(paintPanel.getWidth(), paintPanel.getHeight(),BufferedImage.TYPE_INT_ARGB);
Graphics2D g2d = image.createGraphics();
paintPanel.paintAll(g2d);
							
//Tworzenie pliku zapisu w folderze projektu
File outputfile = new File("saved.png");
							
//Zapis do pliku
try {
	ImageIO.write(image, "png", outputfile);
} catch (IOException e) {
	System.out.println(e.getMessage());
}

Wczytywanie z pliku z kolei może wyglądać następująco:

//Tworzenie obiektu BufferedImage
BufferedImage image = null;
					
//Tworzenie pliku wejściowego 
File inputFile = new File("saved.png");
					
//Odczytywanie pliku wejściowego 
try {
	image = ImageIO.read(inputFile);
} catch(IOException ex) {
	System.out.println(ex.getMessage());
}
					
//Autorska funkcja dodająca odczytany obraz do panelu rysowania paintPanel
paintPanel.setBackgroudImage(image);

 

Więcej informacji:

Dzięki nasłuchiwaniu zdarzeń z myszy, wiemy dokładnie, gdzie znajduje się kursor myszy i możemy rysować kształty podążając za nim (np. przekazując kolejne współrzędne położenia myszy do listy z przykładu, używając np. e.getX(), e.getY()).