Programowanie aplikacji
Wstęp
Po przygotowaniu oprawy graficznej oraz wszystkich materiałów potrzebnych do realizacji projektu, można przystąpić do wykonania aplikacji w wybranym języku programowania. Na potrzeby przykładowego Przewodnika po największych miastach w Polsce zostanie wykorzystany język ProcessingProcessing.
Pierwszym krokiem powinno być pobranie i zainstalowanie zintegrowanego środowiska programistycznego. (IDE - ang. Integrated Development Environment)(IDE - ang. Integrated Development Environment)
Processing rozwijany jest jako projekt Open Source, natomiast Processing IDE jest rozpowszechniane na licencji GPL. Biblioteki oraz kod dostarczony przez producentów objęte są licencją LGPL, co pozwala autorom na rozpowszechniane swoich prac pod dowolną licencją, bez ograniczeń. Wszystkie wersje środowiska Processing znajdują się na stronie http://processing.org.
Instalacja środowiska
Dla każdego systemu operacyjnego wystarczy pobrać i uruchomić środowisko programistyczne wybierając odpowiednią zakładkę na stronie oprogramowania processing
(https://processing.org/download/?processing).
Na stronie tej można także znaleźć wszelkie informacje dotyczące tego języka, takie jak dokumentacja, przykłady i tutoriale, oraz dodatkowe biblioteki. Jeśli na komputerze zainstalowane jest środowisko uruchomieniowe Java (JRE) oraz JDK, to można wybrać plik instalacyjny „Windows (Without Java)”. Następnie należy wypakować pobrane pliki do wybranego folderu. Kończy to proces instalacji. Warto jednak przed przystąpieniem do pisania pierwszego programu dokonać jeszcze konfiguracji środowiska.
Programowanie w Processing
Twórcy środowiska Processing zalecają, by swoje projekty przechowywać wewnątrz folderu nazwanego przez nich szkicownikiem (ang. sketchbook). Dzięki temu projekty będą dostępne w łatwy sposób z menu File- >Sketchbook. Folder ten jest ważny jednak przede wszystkim z innego powodu. To wewnątrz niego musi się znajdować folder z zewnętrznymi bibliotekami, z których chcemy korzystać (instalacja zewnętrznych bibliotek omówiona jest poniżej). Można jednak samodzielnie wybrać inny folder, korzystając z opcji z menu File->Preferences. Pierwszą rzeczą, jaką nalezy ustawić w oknie Preferences, jest położenie wybranego folderu szkicownika. Powinien on być wybrany tak, aby był do niego łatwy dostęp przy instalowaniu dodatkowych bibliotek. Drugą ważną opcją jest maksymalna dostępna pamięć. W sytuacji, gdy operujemy na dużej ilości plików (zwłaszcza graficznych, wideo czy audio) możemy mieć do czynienia z błędem java.lang.OutOfMemoryError, którego można uniknąć poprzez zwiększenie maksymalnej pamięć dla środowiska Processing (co jest analogiczne do zwiększania ilości pamięci dla maszyny wirtualnej Javy, bo przecież Processing powstał jako opakowanie właśnie na Javę, a obecnie rozwinął się do obsługi Android SDK oraz JavaScript). Pozostałe opcje nie są już tak istotne i mogą być dowolnie konfigurowane.
Przykłady wykorzystania języka processing
Prostota składni języka Processing, pozwoliła wielu osobom, które nie miały doświadczenia programistycznego w realizacji projektów bez konieczności zatrudniania specjalistów z branży IT. To czysto akademickie rozwiązanie umożliwiło wielu badaczom opracować prototypy i sprawdzić ich działanie w środowisku o bardzo dużych możliwościach graficznych i interakcyjnych bez ponoszenia znacznych nakładów finansowych. W Internecie można znaleźć dużo przykładów zastosowania tej technologii przez różne grupy zawodowe. Wystarczy wspomnieć o projekcie, który wzbudził spore zainteresowanie świata sztuki. Artyści Daniel Franke i Cedric Kiefer opracowali pomysł wirtualnej ruchomej rzeźby z piasku. Rzeźba powstała na podstawie ruchu tancerki, sczytanego przez kontroler Kinect. Animacja została zrealizowana na podstawie analizy w czasie rzeczywistym właśnie w Processing.
Naukowcy ze znanego niemieckiego instytutu Max Planck, zwizualizowali za pomocą Processing dane około 94,000 publikacji z ostatnich dziesięciu lat zgromadzonych w portalu „SciVerse Scopus”. Zaprojektowana i zaimplementowana dynamiczna sieć pozwoliła na zwizualizowanie połączeń pomiędzy instytucjami naukowymi.
Sieć ta pozwoliła na stworzenie interaktywnego środowiska prezentującego aktywność naukową i współpracę instytutu z innymi jednostkami na całym świecie.
Stephan Thiel z uniwersytetu w Adelajdzie zrealizował projekt polegający na nowoczesnym sposobie interpretacji dzieł Shakespeare. Użycie Processing pozwoliło na zliczenie słów i znalezienie istniejących relacji i powiązań pomiędzy dziełami autora.
Przedstawione przykłady miały za zadanie zilustrować szerokie zastosowanie technologii którą poznacie podczas najbliższych lekcji. Zdobyta wiedza pozwoli Wam zrealizować ciekawe a przede wszystkim kreatywne pomysły.
Typy danych
Zmienna jest to miejsce w pamięci, w którym możemy przechowywać dane. W zależności od rodzaju danych zmienna zajmować będzie odpowiedni zakres pamięci. Do najczęstszych typów danych możemy zaliczyć liczby całkowite, liczby rzeczywiste, znak, oraz zmienne logiczne.
Można określić początkową wartość zmiennej już przy jej deklaracji, można też zrobić to później, za pomocą znanego z matematyki operatora przypisania::
nazwa_zmiennej = nowa_wartość_dla_zmiennej;.
Operator = służy do przypisywania nowej wartości zmiennej. Wartość jaką przechowuje zmienna, możesz zmieniać wielokrotnie w trakcie działania aplikacji.
Kolejną rzeczą, jest nazewnictwo zmiennych. Nazwy zmiennych nie mogą zawierać polskich znaków i nie mogą zaczynać się od liczby. Wartość zmiennej można zmieniać wielokrotnie w czasie trwania programu.
Paradygmaty programowania i instrukcje sterujące
Paradygmat programowania o sposób przepływu sterowania i wykonywania programu komputerowego. Do najpopularniejszych technik programowania należy programowanie obiektowe i programowanie funkcyjne. W programowaniu obiektowym paradygmat polega na traktowaniu programu jako zbióru współpracujących ze sobą obiektów. W programowaniu funkcyjnym określamy funkcje, których wykonanie ma prowadzić do wykonania zadania.
Dijkstra proponował użycie tylko trzech rodzajów struktur sterujących:
Sekwencja (lub konkatenacja) — czyli po prostu wykonanie instrukcji w określonej kolejności. W wielu językach rolę „operatora konkatenacji instrukcji” spełnia niepozorny średnik...
Wybór — czyli wykonanie jednej z kilku instrukcji zależnie od stanu programu. Przykładem jest
if‑then‑else
iswitch/case
.Iteracja, czyli powtarzanie instrukcji tak długo, jak długo spełniony (lub niespełniony) jest dany warunek. Chodzi oczywiście o pętle, np. while, repeat‑until, for itp.
Processing dostarcza również standardowe możliwości, które dostępne są w każdym języku programowania takie jak :
komentarze: //, /∗ ... ∗/
To, co następuje po dwóch ukośnikach to tzw. komentarz. Processing tego nie czyta, bo są to tylko informacje dla programistów. Komentarz taki obejmuje wszystko, co znajduje się za znakiem podwójnego ukośnika do końca linii, lub między znakami /?...?/
pętle: for, while,
Instrukcja for będzie powtarzać ciąg instrukcji tak długo, jak długo zdefiniowane warunki będą prawdziwe.for (int i = 40; i < 80; i = i+5) {
line(30, i, 80, i);
}
instrukcje warunkowe: if, if. . .else,
Podstawowym rodzajem instrukcji warunkowej spotykanej w większości języków programowania jest if‑then. Instrukcja ta pozwala na wykonanie określonego bloku kodu w przypadku spełnienia określonego warunku. Jeśli warunek nie jest spełniony zostanie wykonany blok alternatywny. Pomiędzy różnymi językami programowania występują w tej instrukcji nieznaczne różnice składniowe. W Processing instrukcja ta wygląda następująco:if(warunek)
{
ciąg instrukcji wykonywany jeśli warunek jest spełniony
}
else
{
ciąg instrukcji wykonywany jeśli warunek nie jest spełniony
}
instrukcję wyboru: switch,
Podobnie jak if‑then‑else, jest to instrukcja warunkowa, tzw instrukcja wielokrotnego wyboru. W przypadku niej możemy podejmować decyzje na podstawie wartości jednej zmiennej. Najczęściej wykorzystywana jest ona do obsługi zdarzeń (np. sterowanie za pomocą klawiatury). W zależności od wartości parametru uruchamiana jest odpowiednia akcja.
operatory
W poniższej tabeli znajdują się najważniejsze operatory pozwalające wykonywać obliczenia, oraz zmiany stanu zmiennych. Operatory te pozwalają też porównywać zmienne ze sobą.
Kształty
Najprostsze figury geometryczne dostępne w processingu to:
Kolor
Przypomnijcie sobie o modelach koloru, z rozdziału „ Grafika i multimedia”, z podręcznika do gimnazjum.
Wartości jakie przyjmuje każda z funkcji odpowiedzialna za generowanie koloru to np.: background( R, G, B); Gdzie R – jest wartością z przedziału od (0‑255) dla koloru czerwonego G – jest wartością z przedziału od (0‑255) dla koloru niebieskiego B – jest wartością z przedziału od (0‑255) dla koloru zielonego
Oprócz wartości RGB stosowana jest też czwarta wartość – „alpha – A”, określająca przezroczystość koloru. Każda z funkcji odpowiedzialna za kolor może przyjmować więc wartości (R, G, B, A);
Metoda Setup
Processing posiada kilka predefiniowanych metod, które określają strukturę programu i odpowiadają za najważniejsze dla każdego projektu części. Pierwszą taką metodą jest funkcja setup:void setup() {
}
Jest to metoda wejściowa dla naszego programu i wewnątrz niej definiujemy wszelkie ustawienia programu (takie jak rozmiar okna, silnik renderujący itp.), inicjalizujemy obiekty klas i zmienne. Po skompilowaniu programu otrzymamy okno aplikacji o domyślnym rozmiarze 100 na 100 pikseli. Uruchomić program można na trzy sposoby:
poprzez pierwszą od lewej ikonkę na górnym pasku (ikona „play”),
poprzez menu Sketch->Run,
poprzez skrót klawiaturowy Ctrl+R.
Można również samemu ustawić rozmiar okna poprzez następujące wywołanie wewnątrz funkcji setup:size(700, 700);
Jeśli nie określimy silnika renderującego, domyślnie zostanie ustawiony silnik JAVA2D, który pozwala na operacje dwuwymiarowe. Jest on dokładny, lecz nieco wolniejszy niż silnik P2D. Dostępne opcje silników (od wersji 2.0 środowiska Processing.org) to:
P2D (Processing 2D) – silnik dwuwymiarowy wykorzystujący możliwości kart graficznych wspierających OpenGL 2.0.
P3D (Processing 3D) – silnik trójwymiarowy wykorzystujący możliwości kart graficznych wspierających OpenGL 2.0.
PDF – silnik pozwalający zapisać grafikę dwuwymiarową bezpośrednio do pliku PDF, zamiast wyświetlać ją na ekranie. Co więcej, elementy wektorowe zostaną zapisane również jako wektorowe, co umożliwia późniejszą manipulację nimi w programach typu Adobe Ilustrator. Przydatny, gdy potrzebujemy wysokiej jakości grafiki do druku czy edycji.
W starszych wersjach środowiska dostępny jest również:
OPENGL – silnik trójwymiarowy, do którego potrzebny jest sprzęt, który wspiera OpenGL, wykorzystuje bibliotekę JOGL (Java for OpenGL) i pozwala na rendering trójwymiarowy na stronach www.
P2D oraz P3D – były to sprzętowe implementacje nie wykorzystujące silnika OpenGL.
Aby korzystać z możliwości oraz zapisu do PDF, należy zaimportować odpowiednią bibliotekę. Podobnie jest w przypadku OpenGL'a w starszych wersjach środowiska (od wersji 2.0 OpenGL jest podstawową składową środowiska). Rodzaj silnika określa się poprzez podanie trzeciego parametru metody size np.size(400, 200, P3D);
lub:size(400, 200, PDF, „nazwa_pliku.pdf”);
Powyższy kod (pierwszy przykład) jest wszystkim, czego potrzeba, by uruchomić okno dla OPENGL'a
Metoda draw
Drugą istotną funkcją jest funkcja draw:void draw( ){ }
Jest ona wywoływana cyklicznie w każdej klatce działania programu, zatem w niej należy umieścić kod odpowiedzialny za rysowanie i wszelkie operacje, które powinny odbywać się co klatkę. Można prześledzić to na przykładzie. Należy dodać dwie zmienne typu całkowitego do kodu programu, które będą przechowywać położenie rysowanego obiektu:int posx = 0, posy = 0;
Linię tę trzeba umieścić poza omawianymi wcześniej metodami. Teraz w metodzie draw można dodać rysowanie kwadratu i zwiększenie położenia jego wyrysowania: posx = posy++;rect(posx, posy, 50, 50);
Całość powinna wyglądać następująco:int posx = 0, posy = 0;
void setup(){
size(400, 200);
}
void draw(){
posx = posy++;
rect(posx, posy, 50, 50);
}
Aby wyrysować tylko jedną klatkę lub zatrzymać animację należy skorzystać z metody noLoop. Jeśli umieści się ją wewnątrz metody setup, program wywoła metodę draw tylko raz:void setup(){
size(400, 200);
noLoop();
}
Funkcją o działaniu odwrotnym do noLoop jest funkcja loop, która jest domyślnie wywoływana przy starcie programu, (jeśli nie zadeklaruje się inaczej). Jeśli do kodu zostanie dopisane wywołanie tej funkcji na końcu metody draw, to otrzymany efekt będzie taki, jakby w ogóle nie dodano wywołania pary loop i noLoop. Aby powrócić do widoku animacji należy usunąć oba wpisy (loop oraz noLoop).
W powyższym przykładzie kwadrat rysowany jest od lewego górnego narożnika, jednak można to zmienić, przez dodanie, dla wbudowanych funkcji rysujących kształty, ustawienia, czy ma on być rysowny od narożnika, czy względem środka. Dla metody rect wygląda to następująco:rectMode(CENTER);
By powrócić do domyślnego ustawienia, należy skorzystać z metody:rectMode(CORNER);
Analogiczne metody istnieją dla pozostałych metod rysujących – jest to omówione w dokumentacji programu Processing.
Kolor obrysu i wypełnienia zostały ustawione na wartości domyślne. Zmienić je można przy pomocy następujących metod:fill(0);
stroke(255);
W powyższym przykładzie kolor wypełnienia ustawiony jest na czarny, a kolor obrysu na biały. Można także użyć innego koloru, ale zostanie to omówione później. Jeśli wywołanie tych metod zostaną umieszczone w metodzie setup, będą dotyczyły wszystkich rysowanych obiektów tak długo, póki nie zostaną zmienione. Można je także umieścić wewnątrz funkcji draw. Wtedy dotyczyć będą obiektów narysowanych po ich wywołaniu. Dlatego można określić Processing jako maszynę stanową – raz ustawiony stan jest używany globalnie, aż do jego zmiany. Jeżeli chce się korzystać jedynie z wypełnienia lub tylko z obrysu, należy skorzystać z jednej z poniższych metod:noStroke();
noFill();
Wypełnienie i/lub obrys będą wyłączone do ponownego wywołania metody ustawiającej ich kolor (czyli fill lub stroke).
Metoda exit
Ostatnią główną metodą (lecz nie ostatnią dostępną), jest funkcja wyjścia. W niej należy określić wszystkie czynności konieczne do wykonania przed zamknięciem programu. Metoda ta nazywa się exit. W metodzie tej można wyświetlić komunikat podczas zamykania programu:void exit(){
println(„Koniec programu”);
}
Po uruchomieniu programu komunikat ten jest wyświetlany, ale program nie zostanie zamknięty. Należy samemu obsłużyć zamknięcie okna aplikacji, przez dopisanie na końcu programu, instrukcji:super.exit();
Spowoduje to wywołanie domyślnej obsługi metody exit. Metody tej można również użyć w dowolnym miejscu kodu np. w reakcji na działanie użytkownika. Jej wywołanie spowoduje zamknięcie aplikacji. Dla testów można dopisać jej wywołanie np. na końcu metody draw, która teraz powinna wyglądać następująco:void draw(){
background(100);
posx = posy++;
rect(posx, posy, 50, 50);
exit();
}
Aby obejrzeć efekt działania programu, należy jednak usunąć tę linijkę
Własne funkcje i błędy kompilacji.
W celu poprawienia czytelności kodu można rysowanie kwadratu przenieść do osobnej funkcji, i jej wywołanie umieścić w metodzie draw:void draw(){
background(100);
posx = posy++;
rysujKwadrat(posx, posy);
}
void rysujKwadrat(int _x, int _y){
rect(_x, _y, 50, 50);
}
Teraz należy zapisać program. Należy pamiętać, by plik z kodem źródłowym programu znajdował się w folderze o tej samej nazwie co nazwa pliku (czyli plik „test.pde” powinien znajdować się w folderze „test”) - inaczej otrzyma się błąd. Jeśli chodzi o błędy kompilacji w środowisku Processing, warto wspomnieć o jednej, nie do końca oczywistej, właściwości tego środowiska. By to zobrazować, należy wygenerować celowy błąd, poprzez wywołanie nieistniejącej funkcji. Na dole okna pojawia się wtedy treść powstałego błędu oraz dodatkowo numer linii, w której on się pojawił (liczba „10” w prawym dolnym rogu okna). Należy jednak uważać, bowiem w tym miejscu standardowo pojawia się numer bieżącej linii, w programie. Po kliknięciu gdziekolwiek na kod, zniknie zaznaczenie linii błędu i jej numer. Żeby się dowiedzieć o którą linię chodziło, należy ponownie uruchomić program.
Tryby pracy
Od wersji 2.0 środowisko Processing.org oferuje kilka trybów pracy (a nowe są cały czas tworzone i dodawane). Są to:
Standard – podstawowy i domyślny tryb dostępny w Processing'u. Pozwala tworzyć i eksportować aplikacje desktop'owe jednocześnie na różne platformy (Windows, Mac OS, Linux). Umożliwia także zapis projektu jako aplet w środowisku java uruchamiany w przeglądarce (wraz ze wsparciem dla 3D), jednak to rozwiązanie nie jest już rozwijane (zamiast tego zaleca się wykorzystanie trybu JavaScript). Tryb ten zalecany jest jako domyślny podczas nauki i pisania pierwszych programów w środowisku Processing.org.
Android – zaprezentowany po raz pierwszy w wersji 1.5 środowiska Processing.org, był pierwszym dostępnym, poza standardowym, trybem. Umożliwia tworzenie i uruchamianie projektów na urządzeniach z systemem Android. Począwszy od wersji 2.0 środowiska, wspierane są tylko wersje wyższe niż Android 2.3.3 (Gingerbread, API 10).
JavaScript – pozwala tworzyć aplikacje uruchamiane bezpośrednio z przeglądarki przy użyciu biblioteki Processing.js (http://processingjs.org/). To rozwiązanie zastępuje aplety znane z wcześniejszych wersji środowiska.
Tryb można zmienić poprzez menu rozwijane w prawym górnym obszarze oknaprogramu (Ilustracja 5).
Instalacja zewnętrznych bibliotek
Gdy w API Processing'u nie można znaleźć potrzebnej metody czy klasy, warto poszukać dodatkowych bibliotek. Biblioteki takie znajdują się na stronie środowiska - http://processing.org/reference/libraries/ , a także na wielu innych stronach. Samo środowisko zawiera już kilka przydatnych bibliotek jak np. Video, Network czy PDF Export. Jedyne co trzeba zrobić, by zainstalować pobraną bibliotekę, to przekopiować wszystkie pliki (zachowując strukturę folderów stworzoną przez autora biblioteki) do odpowiedniego folderu. Od wersji 1.0 i wyższej, wszelkie biblioteki potrzebne do wykonania projektu umieszcza się w podfolderze „libraries” w folderze „szkicownika” (czyli jako podfolder folderu sketchbook, którego lokalizacja została ustalona wcześniej podczas konfiguracji). By dodać bibliotekę do projektu, wystarczy, że wykonać jeden z poniższych kroków:
skorzystać z menu Sketch->Import Library wybierając żądaną bibliotekę,
wpisując na początku programu słowo „import” i po spacji nazwę biblioteki wraz z pakietem, w którym się znajduje.
Niezależnie od wybranego sposobu, otrzymuje się identyczny efekt. Przykładowo, jeśli chcemy zaimportować bibliotekę do obsługi wideo, na początku pliku należy wpisać:import processing.video.*;
co pozwoli na korzystanie z klas i metod tej biblioteki.
Obsługa klawiatury
Za obsługę klawiatury odpowiada kolejna główna metoda, a mianowicie:void keyPressed() {}
Ustawienie wewnątrz niej obsługi klawiatury zapewnia, że będzie ona wykonywana w każdej klatce. Można jednak pominąć tę metodę i umieścić obsługę w każdej innej metodzie (np. draw), gdyż nie zawiera ona żadnych parametrów. Zdarzenie klawiatury nie jest przekazywane do metody, a zachowywane w zmiennych globalnych, które zostaną omówione w dalszej części. Ta metoda gwarantuje jednak wywołanie w każdej klatce i estetykę kodu. Zamiast niej można sprawdzać flagę naciśnięcia klawisza o tej samej nazwie co metoda (czyli keyPressed), w następujący sposób:void draw() {
if(keyPressed) {
//tutaj właściwa obsługa klawiatury
}
}
Zaleca się jednak korzystanie z metody keyPressed. Aby sprawdzić, który klawisz został wciśnięty, należy użyć zmiennej globalnej key:switch(key){
case 'a': bgColor = 0; break;
case 's': bgColor = 100; break;
case 'd': bgColor = 200; break;
}
Można dodać także zmienną bgColor, która będzie odpowiadać za zmianę koloru tła i przekazać ją jako parametr do funkcji background w metodzie rysującej. Dzięki temu, po naciśnięciu klawisza „a” tło zmieni się na czarne, po naciśnięciu klawisza „s” na ciemnoszare, a naciśnięcie klawisza „d” zmieni je na jasnoszare. Co jednak zrobić, gdy jest to klawisz specjalny, jak np. strzałka czy enter? Nalezy wówczas skorzystać z kolejnej zmiennej globalnej – keyCode. Zawiera ona kod ASCII klawisza, który został wciśnięty. Aby więc dopisać obsługę zatrzymania i wznowienia animacji przy pomocy strzałek na klawiaturze należy napisać kod:if (key == CODED) {
switch(keyCode){
case UP: loop(); break;
case DOWN: noLoop(); break;
}
}
Powyższy przykład zawiera pewne stałe zdefiniowane w języku Processing. Łącząc oba przypadki poprzez dodanie słowa kluczowego „else” przed obsługą zwykłych klawiszy, otrzymamy kompletną obsługę klawiatury.
Obsługa myszy
Obsługa myszy, tak jak w przypadku klawiatury, możliwa jest na dwa sposoby. Najczęściej wykorzystuje się zmienne globalne (dostępne w każdym miejscu programu) przechowujące położenie kursora myszy: mouseX
oraz mouseY
. Po zakomentowaniu wywołania utworzonej poprzednio funkcji rysującej kwadrat, można napisać wywołanie rysowania z położeniem kursora myszy jako pozycją tworzonego kwadratu. Dodając do tego poznaną wcześniej metodę, by rysować kwadrat od środka, otrzyma się efekt podążania kwadratu za kursorem myszy (kursor w środku obiektu). Fragment kodu za to odpowiedzialny powinien wglądać następująco://rysujKwadrat(posx, posy);
rectMode(CENTER);
rect(mouseX,mouseY,50,50);
Główną metodą, odpowiadającą za obsługę kliknięcia myszy jest mousePressed bez żadnych parametrów. Jeśli umieśi się ją w programie i przeniesie do niej napisane przed chwilą dwie linie rysujące kwadrat, to otrzyma się białe tło i „mignięcie” kwadratu przy kliknięciu przycisku myszy. Dlaczego tak się dzieje? Co należy zmienić w kodzie, by rysowany kwadrat pozostawał widoczny? Trzeba zakomentować (lub usunąć) pierwszą linię w metodzie draw, czyli wypełnienie tła kolorem. Alternatywnie można ją przenieść do metody setup, dzięki czemu będzie wywołana tylko raz z zadanym kolorem jako parametr. Teraz rysowany na kliknięcie kwadrat powinien pozostawać na obrazie. Dodatkowo można sprawdzić, który przycisk został wciśnięty. Do tego celu służy kolejna globalna zmienna - mouseButton. Najlepiej zobrazuje to poniższy przykład:switch(mouseButton){
case LEFT: println(„Wciśnięto LEWY przycisk”); break;
case RIGHT: println(„Wciśnięto PRAWY przycisk”); break;
case CENTER: println(„Wciśnięto ŚRODKOWY przycisk”); break;
}
Inne przydatne podczas obsługi myszy metody to:
mouseClicked() - dla obsługi kliknięcia (w przeciwieństwie do naciśnięcia jest to złożenie dwóch akcji – wciśnięcia i puszczenia przycisku myszy),
mouseDragged() - dla obsługi przeciągania,
mouseMoved() - dla obsługi ruchu myszą (bez wciśnięcia przycisku myszy),
mouseReleased() - odwrotne niż mousePressed() - puszczenie przycisku myszy.
Operowanie na plikach wektorowych
Transformacje w języku Processing są analogiczne do znanych z OpenGL, a mianowicie przekształcany jest cały obecny układ współrzędnych. Zatem narysować obiekt w punkcie (100, 200) można na dwa sposoby:rect(100, 200, 50, 50);
lubtranslate(100, 200);
rectMode(CENTER);
rect(0, 0, 50, 50);
Pierwszy przypadek jest oczywisty, natomiast w drugim najpierw przenosi się cały układ do zadanego punktu, a następnie rysuje obiekt. Aby narysować kwadrat również w punkcie (100, 200), ale obrócony o 45 stopni, trzeba skorzystać ze złożenia przekształceń – translacji oraz obrotu – w następujący sposób:translate(100, 200);
rotate(PI/4);
rectMode(CENTER);
rect(0,0,50,50);
Zastosowana metoda obrotu jako argument przyjmuje kąt obrotu w radianach. Jeśli kąt obrotu oktreślony jest w stopniach, to należy skorzystać z metody radians(), konwertującej stopnie na radiany. Obrazuje to kod:rotate(radians(45));
Można przenieść teraz ten kod do funkcji rysującej na kliknięcie myszy tak, by zamiast stałego punktu, kwadrat rysował się w miejscu, w którym zostanie wciśnięty przycisk myszy (oczywiście obrócony o 45 stopni). Efekt widoczny jest na ilustracji 1, na której można zauważyć, że narysowane kwadraty są „poszarpane” na brzegach (widoczne zwłaszcza na powiększeniu). Aby tego uniknąć należy zastosować metodę smooth(), którą trzeba dodać do ustawień programu (czyli metody setup).
Trzecią przydatną transformacją jest skalowanie – metoda scale, której przekazuje się liczbę skali wzdłuż osi X oraz Y:scale(2,1);
Dopisując powyższą linię do kodu (po translacji i obrocie), otrzyma się obrócony prostokąt. Aby narysować obrócony kwadrat (jak powyżej) w zadanym punkcie, a potem kwadrat (nie obrócony) w punkcie (0, 0) należy napisać kod:pushMatrix();
translate(100,200);
rotate(radians(45));
rectMode(CENTER);
rect(0,0,50,50);
popMatrix();
rect(0,0,50,50);
Operowanie na obrazach rastrowych.
Aby pracować na poziomie pikseli obrazu, należy wykonać kilka kroków, a mianowicie:
załadować piksele, dokonać zmian na nich, zaktualizować piksele na obrazie. Ten proces pokazany jest na przykładzie funkcji zmieniającej wyświetlany obraz na negatyw.void invertColors(){
loadPixels();
for(int x=0; x<width; x++){
for(int y=0; y<height; y++){
float R = 255 – red(pixels[x+y*width]);
float G = 255 – green(pixels[x+y*width]);
float B = 255 – blue(pixels[x+y*width]);
pixels[x+y*width] = color(R,G,B);
}
}
updatePixels();
}
Metoda loadPixels() służy do załadowania pikseli obrazu do tablicy pixels, którą później wykorzystuje się do przeprowadzenia zmian. Jest to tablica jednowymiarowa, a więc można zastosować następującą pętlę:for(int i=0; i<pixels.length; i++){
float R = 255 – red(pixels[i]);
float G = 255 – green(pixels[i]);
float B = 255 – blue(pixels[i]);
pixels[i] = color(R,G,B);
}
Jednak częściej trzeba poruszać się po obrazie w dwóch wymiarach. Wymaga to indeksowania tablicy jednowymiarowej tak, jakby była dwuwymiarową. Globalne właściwości width oraz height odnoszą się do wymiarów obrazu (są identyczne z wartością ustawioną poprzez size()). Metody red, green oraz blue pozwalają pobrać odpowiednie składowe koloru, by je zmodyfikować. Po dokonaniu zmian (odwróceniu koloru), można zapisać kolor piksela przy wykorzystaniu metody color z parametrami, odpowiednio – składową czerwoną, zieloną i niebieską koloru. Ostatnim krokiem jest zaktualizowanie pikseli wyświetlanych na ekranie – updatePixels(). Jest to technika podobna do podwójnego buforowania w OpenGL. By uniknąć nieprzyjemnych efektów zmiany piskeli na obrazie podczas jego wyświetlania, należy je zmodyfikować w osobnym buforze (tablica pixels), po czym zamienić te tablice.
By zobaczyć efekt, trzeba dopisać wywołanie utworzonej funkcji na końcu metody obsługującej wciśnięcie przycisku myszy (zatem po każdorazowym narysowaniu kwadratu powinno się otrzymać negatyw obrazu). Analogicznie można pracować na pikselach wczytanych obrazów. Należy w tym celu utworzyć zmienna globalną, która będzie przechowywała wczytany obraz.PImage img;
Typ PImage to typ danej obrazu. W metodzie setup należy wczytać przykładowy obraz do zmiennej:img = loadImage(„test.jpg”);
W tym miejscu można użyć dowolnego obrazu (obsługiwane typy plików to .gif, .jpg, .tga, .png). Przy tak podanej ścieżce musi się on znajdować w folderze projektu (tam gdzie plik .pde). Aby ustawić go jako tło programu, należy dodać następującą linię:image(img, 0, 0);
Kod ten umieszcza się w metodzie setup zaraz po załadowaniu obrazka. Gdyby został on umieszczony w metodzie draw, to za każdym razem przysłaniane byłyby kwadraty rysowane przy wciśnięciu klawisza myszy. W ten sposób wskazany obrazek zostanie wyrysowany począwszy od punktu (0, 0). Oprócz położenia, jako kolejne dwa parametry można przekazać do metody image rozmiar, do jakiego należy przeskalować obrazek, np.image(img, 0, 0, 100, 100);
Dodatkowe informacje na temat tej metody można znaleźć w dokumentacji
http://processing.org/reference/image_.html
Na pikselach obrazka również można pracować w sposób analogiczny do opisanego powyżej (który dotyczył pikseli całego obrazu). Różnica polegać będzie na wskazaniu odpowiedniej tablicy pixels, np.img.loadPixels();
for(int i=0; i<img.pixels.length; i++){
float R = 255 – red(img.pixels[i]);
float G = 255 – green(img.pixels[i]);
float B = 255 – blue(img.pixels[i]);
img.pixels[i] = color(R,G,B);
}
img.updatePixels();
Tak zmodyfikowany obraz należy ponownie wyświetlić przy użyciu metody image.
Wczytywanie i zapis plików
Aby zapisać utworzony plik obrazu stosuje się instrukcję:saveFrame(„output.png”);
Zapisanie więcej niż jednego obrazu (jednej klatki) lub serii klatek, można wykonać poprzez odpowiednią konstrukcję nazwy pliku wyjściowego:saveFrame(„output-###.png”);
Spowoduje to zapis każdej klatki do pliku z odpowiednią nazwą tj. output‑001.png, output‑002.png itd. Jednak przed uruchomieniem powyższego przykładu należy dodać w metodzie setup jeszcze ustawienie ilości klatek na sekundę:frameRate(1);
= loadStrings(„plik.txt”);
Kolejne linie pliku zostaną wczytane jako osobne elementy tablicy typu String. Załóżmy, że każda linia zawiera dane oddzielone od siebie pewnym charakterystycznym znakiem, np. „:”. By uzyskać dostęp do poszczególnych treści (czyli rozdzielić linię na fragmenty oddzielone dwukropkami) można się posłużyć metodą splitTokens, w następujący sposób:String[] daneLinii = splitTokens(linie, „:” );
podając jako parametr ciąg znaków do podziału oraz znak (lub znaki, bo możemy podać ich kilka naraz) podziału. Zapis treści tekstowych dokonuje się następująco:saveStrings(„nazwa_pliku.txt”, tresc_do_zapisu);
gdzie tresc_do_zapisu to tablica (tablica String[]), w której każdy element to kolejna linia pliku, który zapisujemy. Aby wybrać plik do wczytania podczas działania programu należy napisać następujący kod:String sciezka_do_pliku = selectInput();
if (sciezka_do_pliku == null) {
println(„Nie wybrano żadnego pliku...”);
}
W pierwszej linii zostaje otworzone okno dialogowe do wyboru pliku i wskazana zmienna, do której ma być przypisana ścieżka wybranego pliku. Następnie należy sprawdzić, czy plik został wybrany, czy naciśnięto przycisk „anuluj” lub pojawił się inny błąd, co spowodowało, że zmienna jest pusta. Wtedy można wyświetlić stosowny komunikat (jak w powyższym przykładzie). Jeśli nie ma błędu, można przekazać ścieżkę do pliku do odpowiedniej metody wczytującej (omawianej wcześniej). Wczytywać dane można z wielu innych formatów plików oraz źródeł – wystarczy zajrzeć do dokumentacji lub przejrzeć dodatkowe biblioteki. Często wykorzystywany jest format XML.