Twitter: @grzegg
Kategoria: java, Tagi: - - - - - - .

Java [39] – JavaFX cz. 5: rysowanie 2D

Z rysowaniem figur, a właściwie figury, spotkaliśmy się już przy okazji pierwszej lekcji poświęconej JavieFX. Możliwości JavyFX w tym względzie nie kończą się oczywiście na rysowaniu kółek.

Ogólne informacje

JavaFX udostępnia zestaw klas umożliwiający dość łatwe rysowanie dwuwymiarowych kształtów, są one dostępne w pakiecie javafx.scene.shape. Warto na początku przygody z grafiką 2D przejrzeć choćby pobieżnie dokumentację pakietu, żeby dowiedzieć się jakie figury możemy wykorzystać. Należą do nich na przykład linie (Line), koła (Circle), elipsy (Ellipse), prostokąty (Rectangle), łuki (Arc) czy krzywe Béziera (CubicCurve). W tej lekcji nie będziemy wszystkich omawiać, spróbuję jednak pokazać jak zacząć zabawę w rysowanie figur i trochę bardziej złożonych obrazków.

Przeglądając dokumentację pakietu zauważysz zapewne wskazującą na możliwości tworzenia także trójwymiarowych obiektów ale nimi, przynajmniej na razie, nie będziemy się zajmować. Nie będziemy też w tej lekcji zajmować się grafiką bitmapową, taką jak zdjęcia. Skupimy się po prostu na rysowaniu prostych figur i bardziej złożonych kształtów.

Obiekty reprezentujące figury mają oczywiście swoje właściwości, do podstawowych należą oczywiście te, które określają ich wymiary i położenie, wypełnienie, parametry obrysu. Co ważne, w przeciwieństwie do wielu kontrolek, kształty nie zmieniają automatycznie swoich wymiarów, kiedy zmienia się wielkość okna.

Zanim zaczniemy rysować na ekranie, dobrze by było zapoznać się z układem współrzędnych który będziemy wykorzystywać. W przeciwieństwie do tego znanego nam z lekcji matematyki, zaczyna się w lewym górnym rogu obszaru rysowania, a kolejne wartości współrzędnych odpowiadające pikselom zwiększają się w prawo (oś X) i w dół (oś Y). Rysując w scene, mamy do dyspozycji obszar określony wielkością tego węzła. Szerokość i wysokość scene (podobnie jak wielu innych elementów w JavieFX), można pobrać za pomocą metod: scene.getWidth() i getHeight().


Stwórz nowy projekt JavaFX w NetBeans, nazwij go Grafika2D i zmodyfikuj domyślny kod:

Po uruchomieniu programu pojawi się okienko z przyciskiem, kiedy w niego klikniesz w terminalu zostanie wypisana szerokość i wysokość węzła scene. Zmieniaj rozmiary okna i wciskaj przycisk, za każdym razem otrzymasz aktualne rozmiary. Pobieranie bieżących wymiarów może się przydać na przykład jeśli chcemy umieścić obiekt w określonym położeniu w relacji do brzegów okna, na przykład dokładnie w środku.

Teraz pokażę na kilku przykładach jak rysować proste figury geometrycznych a przy okazji jak modyfikować ich wygląd. Zaczniemy od rysowania linii.

Linie

Linie to najprostsze z figur. Kropka to w JavieFX też linia tyle, że zaczynająca się i kończąca w tym samym miejscu. O położeniu i długości linii decydują dwie pary parametrów: startX, startY, endX i endY. Dwa pierwsze oznaczają początek linii, druga jej koniec.


Uzupełnimy zatem kod metody start():

Oczywiście należy jak zawsze zadbać o zaimportowanie odpowiednich klas.

Pokazałem tu dwa sposoby tworzenia linii, korzystające z dwu konstruktorów. Pierwszy z nich przyjmuje jako argumenty współrzędne początku i końca linii, drugi konstruktor nie przyjmuje żadnych argumentów a wartości odpowiednich właściwości ustawiamy używając odpowiednich metod: setStartX(), setStartY(), setEndX() oraz setEndY().

Nasza aplikacja będzie teraz zawierała dwie linie:


Jak widać domyślnie linie mają grubość 1 piksela i są czarne. Spróbujemy teraz nieco zmienić jedną z nich.
Poniżej ostatniej linii ustawiającej parametry linii linia2 dodaj taki kod:

Efekt wygląda tak:


Zwróć uwagę, że nazwy metod których powyżej używałem mają w nazwie człon Stroke oznaczający kreskę. W przypadku figur, jak zaraz pokażę oznacza kreskę obrysu. Najpierw użyliśmy metody setStroke przekazując jej jako argument kolor. Więcej na temat kolorów napiszę za chwilę. Na razie wpisując w NetBeans Color. możesz skorzystać z podpowiedzi i zobaczyć inne nazwy kolorów, które można wykorzystać.

Metoda setStrokeLineCap() pozwala określić rodzaj zakończenia linii. Mamy tu trzy możliwości:

  • BUTT – brak dekoracji
  • SQUARE – na końcach zostają dodane prostokąty
  • ROUND – końce są zaokrąglone

Należy pamiętać, że SQUARE i ROUND wydłużają nieco linie.

Ostatnia z metod, setStrokeWidth() odpowiada oczywiście za ustawienie grubości linii.

Teraz dodajmy kropkę:


Upss… dziwnie kwadratowa ta kropka. Zmieńmy zatem zakończenia linii:

Teraz jest już lepiej:


Linia niekoniecznie musi być ciągła, można tu zastosować różne wzorki, które definiujemy jako obserwowalną listę. Kolejne liczby oznaczają długość kolejnych linii i przerw między nimi. Zwróć uwagę, że jeśli liczba liczb jest nieparzysta to dana liczba oznacza na zmianę długość odcinka kreski i przerwy:

Można też zrobić to krócej:

Efekt wygląda tak:


Prostokąty

Aby narysować typowy prostokąt, o ostrych kątach podajemy cztery parametry: x i y wyznaczające lewy górny róg oraz width i height określający jego szerokość i wysokość.


Możemy w tym celu używać jednego z czterech konstruktorów zdefiniowanych w klasie Rectangle, który wygląda tak:


Rectangle(double x, double y, double width, double height)

Można też użyć konstruktora Rectangle() a później używając odpowiednich metod (modyfikatorów) ustawić odpowiednie wartości. Trzecią możliwością jest przekazanie konstruktorowi szerokości i wysokości prostokąta, wtedy zostanie on utworzony w domyślnym miejscu. Użyjemy jednak pierwszej opcji:


Domyślnie prostokąt ma czarne wypełnienie, co oczywiście można zmienić:


Można też zmienić obrys:


Jak widać, zastosowaliśmy te same metody co w przypadku linii. Podobnie możesz modyfikować inne parametry obrysu.

Prostokąt może mieć zaokrąglone rogi, za co odpowiadają dwie właściwości: arcWidth i arcHeight.


Ustawiamy je używając odpowiednich metod – modyfikatorów:


Koła

Klasa Circle oferuje pięć konstruktorów służących tworzeniu kół. Położenie i rozmiar koła określają trzy parametry: centerX, centerY oraz radius. Jak same nazwy wskazują, dwa pierwsze określają położenie środka koła a trzeci jego promień.


Dodajmy zatem koło do naszego obrazka:


Elipsy

Przy rysowaniu elipsy, jak wynika z dokumentacji, oprócz współrzędnych środka, musimy określić dwie średnice, pionową i poziomą (radiusX, radiusY).


Nasza elipsa będzie dla odmiany pozbawiona wypełnienia, za to będzie z obrysem:


Wielokąty

Klasa Polygon pozwala rysować dowolne figury zamknięte. Za pomocą konstruktora, lub metod (add, addAll) podajemy współrzędne kolejnych punktów, między którymi rysowane są linie, po czym ostatni punkt zostaje połączony z pierwszym.


Stwórzmy więc pięciokąt:

Mamy pięciokąt:


Można dodać do wielokąta kolejny punkt:

Ale trzeba pamiętać, że punkt dodaje się na końcu, więc do tego punktu biegnie krawędź z punktu, który był poprzednio ostatni (40, 120) i od dodanego punkty pobiegnie odcinek zamykający figurę do punktu pierwszego (80, 80). Dlatego efekt, który otrzymamy będzie nieco dziwny:


Linia łamana

Polyline to klasa służąca do rysowania linii łamanych, działa podobnie do wielokąta z tym, że ostatni punkt nie łączy się z pierwszym.


Można też używać wypełnienia:

Wtedy efekt będzie taki:


Kolory

Dotychczas nadając kolor obrysom czy wypełnienia robiliśmy to w taki sposób: Color.NAZWAKOLORU. Czyli używaliśmy nazw kolorów, które chcieliśmy zastosować. Teraz zajrzyj do dokumentacji klasy Color. Można tam znaleźć, w sekcji „Field Summary” nazwy wszystkich nazw kolorów, które możemy użyć, ich próbki a także ich wartości RGB.

Jak wynika z dokumentacji kolory można definiować też inaczej, jako wartości RGB, przy czym można stosować zakres 0-1 lub 0-255 dla każdej składowej barw. Można też regulować kanał alfa, który oznacza stopień krycia (wartość 0 oznacza całkowitą przeźroczystość). Możliwe są też inne sposoby określania barw, ale nie będę ich tu poruszał, jak zwykle zachęcam do przestudiowania dokumentacji.

Można na przykład tak zmienić wypełnienie obiektu lamana:

Krótsza wersja tego kodu wygląda tak:

Teraz jest ona ciemnozielona, półprzeźroczysta. Użyliśmy tu metody statycznej rgb, przyjmującej trzy wartości odpowiadające kanałom RGB (Red, Green, Blue) w zakresie 0-255 oraz kanału alfa z zakresu 0-1. Metoda rgb jest przeciążona, można nie podawać wartości kanału alfa, wtedy domyślnie ma on wartość 1.


Następnie zrobimy rzecz bardziej zabawną, dodaj taki kod:

Od tej chwili, jeśli klikniemy na obiekt kolo to przyjmie on losową barwę i stopień krycia, ponieważ wszystkie wartości kanału RGB oraz alfa są losowane. Zauważ, że w tym przypadku używamy konstruktora klasy Color, który przyjmuje wszystkie wymienione wartości z zakresu 0-1.


Kod pliku Grafika2D.java

Inne sposoby wypełniania kształtów

Kształty niekoniecznie musimy wypełniać jednolitym kolorem. Klasa Paint, która jest klasą nadrzędną klasy Color posiada też inne klasy potomne: LinearGradient, RadialGradient oraz ImagePattern, które odpowiednio umożliwiają wypełnienie obiektów gradientem liniowym, radialnym (kołowym) oraz bitmapami lub wzorkiem. Niestety nie będziemy tu szerzej omawiać tych możliwości (może innym razem).

Przykład i zadanie

Na koniec wykorzystamy dotychczas zdobyte umiejętności do narysowania wzoru o charakterze fraktalnym. Wykorzystamy przy tym technikę rekurencji.

Stwórz kolejny projekt o nazwie KolkaRekurencjaFX a w nim umieść kod (w pliku KolkaRekurencjaFX.java):

Po uruchomieniu programu otrzymasz taki rezultat:


Jak widać za pomocą dość krótkiego programu można uzyskać całkiem ciekawe efekty.

Przeanalizuj teraz powyższy kod, tak aby zrozumieć jak działa. Zmodyfikuj go tak aby uzyskać inne ciekawe efekty, na przykład:

  • Otrzymaj z kółek „krzyż” taki jak na ilustracji poniżej

  • Dodaj do interfejsu kontrolki pozwalające zmieniać pewne parametry rysunku, np. początkową szerokość koła, dzielnik promienia, możesz też dodać dodatkowe parametry.

Leave a Reply