Popularny post Leoneq Napisano Marzec 16, 2021 Popularny post Udostępnij Napisano Marzec 16, 2021 Ten artykuł jest częścią serii "Kurs programowania w Processing" #1 - Wstęp, kształty, debugger #2 - Piksele 2D oraz interaktywność #3 - Tekst, pliki, dźwięk, przekształcenia #4 - OpenGL, Arduino! W kolejnej części kursu, opowiemy sobie o funkcjach i mechanizmach rysowania w Processingu, kilku wbudowanych funkcjach, i spróbujemy zrobić prosty kalkulator. Zaczynajmy! Ten artykuł bierze udział w naszym konkursie! 🔥 Na zwycięzców czekają karty podarunkowe Allegro, m.in.: 2000 zł, 1000 zł i 500 zł. Potrafisz napisać podobny poradnik? Opublikuj go na forum i zgłoś się do konkursu! Czekamy na ciekawe teksty związane z elektroniką i programowaniem. Sprawdź szczegóły » Rysowanie W poprzedniej części przyswoiliśmy kilka prostych funkcji, pozwalających rysować podstawowe kształty. Jeżeli dla kogoś nie było klarowne jak to „działa”, to teraz omówimy mechanizm rysowania. Przede wszystkim, należy sobie wyobrazić okienko naszego programu jako płótno, po którym można malować. I żeby było łatwiej, wyobraźmy sobie że za nas rysuje robot, który wykonuje nasze polecenia. Zaczynając od pierwszego programu z poprzedniej części: size(200, 200); background(255); stroke(0); circle(100,100,40); Najpierw mówimy robotowi, że nasze płótno ma mieć rozmiar 200x200 pikseli (odpowiednio szerokość, i wysokość), oraz białe tło. Następnie robot bierze do ręki czarny pędzel (stroke), i wie, że ma nim malować kontury kształtów. Aby narysować okrąg, mówimy robotowi teraz żeby po prostu narysował go – o średnicy 40 pikseli, ze środkiem odległym o 100 pikseli w x i 100 pikseli w y od lewego górnego rogu. Można powiedzieć, że nasze płótno to układ współrzędnych, ze środkiem w lewym górnym rogu. Jeżeli chcemy mieć czerwone koło z białym konturem na czarnym tle, program powinien wyglądać tak: size(200, 200); background(0); stroke(255); fill(255,0,0); circle(100,100,40); Dla niektórych może nie być jasne, dlaczego polecenie "wypełnij", jest przed "pomaluj koło". Jeszcze raz: robot bierze tym razem biały pędzel, czerwone wiaderko farby, i dopiero tymi narzędziami rysuje koło. Warto zapamiętać taką zasadę, że najpierw mówimy jak takie koło ma wyglądać, a dopiero potem je malujemy. Pozwoli to uniknąć błędów! Uzupełniając jeszcze zbiór kształtów: możemy narysować trójkąt. triangle(x1, y1, x2, y2, x3, y3); x1, y1 - współrzędne pierwszego wierzchołka, x2, y2 - drugiego, i x3 oraz y3, to współrzędne trzeciego wierzchołka. Grubość kreski zaś możemy zmienić funkcją strokeWeight(x); po zbiór wszystkich funkcji jakie mamy do dyspozycji, warto zajrzeć do oficjalnej dokumentacji. noStroke() usunie kontur w ogóle. Kolory Polecenia przyjmujące kolor (np. fill, stroke, background) mogą przyjmować albo jedną zmienną, albo trzy, a czasem nawet cztery lub dwa. Zmienne te są pojedynczymi bajtami ośmiobitowymi (typu byte, char), więc przyjmują wartości od 0 do 255 – gdzie zero oznacza minimalną wartość, 255 maksymalną wartość. Dla przypomnienia, konkretnie byte/char ma 8 bitów, więc obraz czarnobiały będzie z głębią ośmiobitową, gdzie kolorowy – już 24 bity. Zmienne typu int mogą mieć wartości większe od zakresu char/byte, więc aby ją wykorzystać, trzeba ją zmodyfikować - o czym za chwilę. Wracając do samych funkcji; możemy podać tylko jedną wartość, wtedy obraz będzie narysowany w skali szarości. Przy podaniu dwóch wartości, pierwsza będzie kolorem w skali szarości, a druga alfą - przezroczystością. Możemy także podać 3 wartości; komponent czerwony, zielony i niebieski, które po wymieszaniu mogą nam dać praktycznie dowolny kolor. Może się tutaj przydać wbudowany w Processing wybierak barw: Czasem przydatne może być podanie czwartej wartości, alfy. Jest to przezroczystość – gdzie 0 oznacza że kształt jest całkowicie przezroczysty, a 255 – całkowicie nieprzezroczysty. fill(127); //50% jasności fill(255, 127); //biały półprzezroczysty fill(255, 0, 255); //fioletowy fill(255, 0, 0, 127); //czerwony półprzezroczysty W wybieraku są jednak jeszcze 3 wartości: H, S i B. Oznaczają one kolejno hue (odcień), saturation (nasycenie), oraz brightness (jasność, czasem value). Tymi trzema wartościami także można definiować kolory, zamiast R, G i B. Źródło: https://nycdoe-cs4all.github.io/images/lessons/unit_1/3.2/pie.png Wizualnie możemy to przedstawić jako taki walec. Dla niektórych taka postać może być bardziej naturalna, więc może kolor podawać także w tych wartościach: colorMode(HSB); Wtedy należy będzie podawać kolejno wartości HSB w przedziale 0-255. Jeżeli jednak chcemy jeszcze "naturalniej" podawać kolory, możemy podać zakres wartości: colorMode(HSB, 360, 100, 100); //podaliśmy kolejno hue, saturation i brightness Dzięki tej funkcji, kolor możemy definiować zgodnie z naszym walcem: jako że koło ma 360 stopni, możemy wybrać kolor tak jak jest na walcu, a nasycenie i jasność podać w procentach, od 0 do 100. Przydatnym trikiem może być funkcja constrain(a, min, max); która ograniczy liczbę a do podanych wartości (dlatego np. nie będzie miała więcej niż 255). Inną przydatną funkcją może być map(a, min1, max1, min2, max2); Która przekonwertuje wartość a z jednej skali do drugiej (np. wartość 100 ze skali 0-200 do 500 w skali 0-1000, czyli 50%). Przede wszystkim, kolor należy brać jako rodzaj zmiennej. Dlatego możemy zrobić coś takiego: color x = #ff0000; color y = color(255, 0, 0, 0); Możemy tym sposobem przypisywać wartości kolorów, albo robić zmienne zawierające z góry ustalone kolory. Tekst Poza prostymi kształtami, dobrze by było umieć narysować tekst. Processing pozwala korzystać z wbudowanej czcionki, oraz własnej; na razie będziemy korzystać z domyślnej. text(tekst, x, y); Narysuje zmienną "tekst", która może być zarówno liczbą, jak i Stringiem. "x" jest liczony od lewej krawędzi tekstu, a co ważne, y jest liczony od dolnej krawędzi tekstu. Co jeszcze ważniejsze, kolor nie definiujemy przez "stroke", a "fill" ! Tekst możemy narysować także w prostokącie. Jeżeli tekst będzie za długi, zostanie ucięty. Pierwszy parametr to zmienna do wyświetlenia, dwa następne to odpowiednio x i y lewego górnego rogu prostokąta, a dwa następne to szerokość i wysokość prostokąta. text(tekst, x1, y1, szerokosc, wysokosc); Rozmiar tekstu definiujemy przez podanie wysokości w pikselach: textSize(a); Odstęp pomiędzy liniami tekstu możemy zmienić tą funkcją: textLeading(a); Możemy także zmienić miejsce od którego będzie rysowany tekst - od lewej krawędzi, środka, i prawej krawędzi. textAlign(LEFT); textAlign(CENTER); textAlign(RIGHT); Bardzo przydatna może się jeszcze przydać funkcja zwracająca długość tekstu przed jego wyświetleniem. int dlugosc = textWidth(zmienna); Krzywe Bardzo często będziemy wykorzystywać krzywe w celu narysowania wykresu, konkretnego kształtu itp. W Processingu możemy narysować 3 rodzaje krzywych. Zaczynając od zwykłego łuku - łuk jest wycinkiem elipsy. Dlatego będziemy go rysować podobnie jak elipsę; pierwsze 4 zmienne są zatem takie same, jakbyśmy rysowali elipsę (kolejno współrzędne lewego górnego rogu prostokąta, szerokość i wysokość). Dochodzą jeszcze 2 parametry, kąt startowy i końcowy. arc(x1, y1, szerokosc, wysokosc, start, stop); Należy pamiętać, że w Processingu kąty są podawane w radianach (pi radianów = 180 stopni), a zero stopni jest z prawej strony prostokąta. Pozostałe krzywe dla bardziej zaawansowanych użytkowników, szczególnie tych, którzy dobrze znają trygonometrię. Krzywą możemy narysować funkcją curve(): curve(punktp1x, punktp1y, punkt1x, punkt1y, punkt2x, punkt2y, punktp2x, punktp2y); Dość szeroka ta funkcja. Do zdefiniowania krzywej potrzebujemy 4 punktów: startowego i końcowego krzywej (punkt1x i y, punkt2x i y), oraz dwóch pomocniczych (punktp1x i y, punktp2x i y). Jak pomocnicze punkty oddziałują na tą krzywą? Tutaj najlepiej samemu się tym pobawić, ponieważ definicja jest trochę długa i mało zrozumiała dla przeciętnej osoby. Najprościej jednak będzie dla nas rysować krzywe Beziera. Trochę łatwiej jest zrozumieć jak one działają: bezier(punkt1x, punkt1y, punktp1x, punktp1y, punktp2x, punktp2y, punkt2x, punkt2y); Cóż, funkcja ta także przyjmuje 4 punkty, jeden startowy i końcowy, oraz 2 pomocnicze. Warto zauważyć, że ich kolejność jest inna. Działanie punktów kontrolnych tutaj jest trochę prostsze: można sobie wyobrazić, że te punkty są połączone z krzywą gumką recepturką. Im dalej pociągniemy tym bardziej wykrzywimy naszą linię. Jeżeli chcemy zdefiniować więcej punktów aby zrobić bardziej wyrafinowaną krzywą, musimy to zrobić w trochę niestandardowy sposób: beginShape(); vertex(x, y); //pierwszy punkt bezierVertex(punktp1x, punktp1y, punktp2x, punktp2y, punktx, punkty) endShape(); Aby zrobić taką długą krzywą, musimy zrobić nowy kształt. BeginShape() powie naszemu robotowi z początku artykułu, że chcemy zrobić "coś większego". Następnie definiujemy pierwszy punkt – vertex – a zaraz po nim kolejne punkty Beziera, ile ich chcemy. Na koniec mówimy robotowi że kształt został narysowany, i że kończymy jego rysowanie funkcją endShape(). W ten sposób możemy też rysować wieloboki, podając zwykłe vertexy - o czym dokładniej w kolejnej części. Należy zwrócić uwagę na wygląd krzywych cure i bezier, a mają takie same punkty początkowe i pomocnicze! (dokonałem tylko przesunięcia w osi y o 100) Krzywymi Beziera można się też pobawić w programie GIMP: Zdjęcia i obróbka pikseli Processing umożliwia także wczytywanie i obróbkę zdjęć, a nawet ich zapis. Przechodząc od razu do kodu: PImage img; //zmienna typu PImage void setup() { size(500, 500); img = loadImage("mysummervacation.jpg"); //robimy nowy obiekt typu PImage, ładując plik do zmiennej } void draw() { background(0); image(img,0,0); //rysujemy obrazek na koordynatach 0,0 } Tym kodem wczytamy i wyświetlimy zdjęcie z pliku. Tworzymy najpierw zmienną typu Pimage, a następnie deklarujemy obiekt przypisując zdjęcie do tej zmiennej. Na końcu zdjęcie wyświetlamy. Troszkę jest to na odwrót zrobione, lecz powinno być to zrozumiałe. Processing wczyta plik który znajduje się w głównym katalogu szkicu - możemy się tam dostać z poziomu menu, lub klikając CTRL+K: Skoro mamy już zdjęcie, to możemy teraz coś z nim zrobić. Na początek wystarczy nam zmiana odcienia: tint(kolor); Gdzie za kolor należy wstawić wartości rgb lub skalę szarości. Mówiąc o zdjęciu jako pliku, powinniśmy raczej myśleć że jest to zbiór pikseli (w grafice rastrowej). Każdy piksel ma swoją wartość i swój numer – piksel zerowy jest w lewym górnym rogu, i co ważne, zdjęcie nie jest tablicą dwuwymiarową, tylko jednowymiarową – czyli każdy piksel ma po prostu swój numer. Jeżeli mamy zdjęcie 5x5 pikseli, pierwszy piksel będzie o numerze 0, ostatni 24, co da nam 25 pikseli. Pierwszy wiersz będzie miał zatem piksele 0-4, a ostatni – 20-24. Przydatny może się okazać wzór: px = x + (y*szerokosc) Który nam określi numer danego piksela z współrzędnych x i y. Tak więc piksel o współrzędnych (4,3) to 4 + 3 * 5 = 19. W processingu mamy bezpośredni dostęp do naszego płótna, czyli zbioru pikseli – lecz przed edytowaniem go, musimy go "załadować": loadPixels(); pixels[i] = color(x); pixels[] to nasze płótno. Możemy traktować je jako zwykłą zmienną typu color. Po zmianie tej wartości, musimy "zaktualizować" płótno aby zobaczyć nowe piksele: updatePixels(); Co więcej, funkcje te możemy wywołać w stosunku do zdjęcia: img.loadPixels(); Możemy dzięki temu stworzyć nasze własne filtry do zdjęć - na przykład wyświetlmy obraz o dwukrotnie większej saturacji, lub wyświetlimy tylko czerwone piksele. Przydatne będą tu funkcje red(), blue() i green() które wyciągną z koloru konkretną wartość. Klawiatura i mysz W poprzedniej części poznaliśmy dwie zmienne programu, mouseX i mouseY, wskazujące koordynaty kursora. W stosunku do myszki mamy jeszcze do dyspozycji zmienną mousePressed(), która zwraca LEFT, CENTER oraz RIGHT, w zależności od wciśniętego przycisku. Oczywiście Processing obsługuje też klawiaturę. Zmienna keyPressed() zwraca logiczne 1, kiedy jest przyciśnięty przycisk. Z kolei zmienna key zwróci wciśnięty znak. Jeżeli chcemy odczytać klawisz funkcyjny, jak alt, ctrl czy delete, przychodzi nam tutaj zmienna keyCode, która może zwrócić: -ALT -CONTROL (CTRL) -SHIFT -UP (strzałka w górę) -DOWN (strzałka w dół) -LEFT (strzałka w lewo) -RIGHT (strzałka w prawo) Pozostałe klawisze jak enter, backspace, esc itd możemy odczytać przez key, ponieważ są one w specyfikacji ASCII. Przydatne mogą być także "eventy", czyli funkcje wywoływane kiedy coś się stanie (przerwania): mousePressed() wywoła się raz, kiedy myszka jest naciśnięta mouseReleased() wywoła się raz, kiedy odklika się myszkę mouseMoved() wywoła się raz, kiedy myszka się przesunie mouseDragged() wywoła się raz, kiedy myszka będzie kliknięta i się przesunie. W przeciwieństwie do mousePressed, te funkcje są wywoływane raz. mousePressed zawsze zwróci jeden, dopóki myszka będzie naciśnięta. Podobnie ma się z klawiaturą: keyPressed() wywoła się raz, kiedy klawisz zostanie naciśnięty keyReleased() wywoła się raz, kiedy klawisz zostanie puszczony Funkcje te mogą tworzyć ciekawe kombinacje z funkcjami loop(), noLoop() I redraw(); jako że funkcja draw() wykonuje się za każdym razem, kiedy jest rysowana klatka programu, możemy to zatrzymać funkcją noLoop(). Powyższe funkcje przerwań dalej będą działać, więc redraw() zaktualizuje klatkę (narysuje ją, wykona kod w draw() tylko raz), a loop() spowoduje że program znowu zacznie się rysować. Do innych ciekawych funkcji należy noCursor(), który schowa kursor jeżeli będzie on w oknie programu. Można tak dzięki temu zrobić własne kursory. Funkcja cursor() przywróci widoczność kursora. Ba, możemy nawet podać wartość w funkcji cursor(), jak: ARROW, CROSS, HAND, MOVE, TEXT, WAIT aby zmienić ikonę kursora na np. klepsydrę lub łapkę. void setup() { size(200, 200); background(255); noStroke(); fill(0); rect(100,0,200,200); } void draw() { if (mouseX > 100) cursor(HAND); else cursor(ARROW); } Ostatnią, i chyba najważniejszą rzeczą, to funkcja save(). Zrobi ona migawkę z tego co właśnie wyświetla się na ekranie. Funkcja ta przyjmuje jeden argument, ścieżkę zapisu. Możemy także zapisać ponumerowane klatki (saveFrame), jeżeli chcemy zrobić z nich animację. Przydatna może być w takim wypadku funkcja frameRate(x), która ograniczy liczbę klatek na sekundę naszego programu. Po przyswojeniu sobie takiej dużej dawki wiedzy, proponuję zrobić kolejny praktyczny program, rysujący wykres wartości występujących w układzie RLC: Żeby nie komplikować sprawy, może to być wykres poglądowy (bez konkretnych jednostek). Program powinien być też responsywny, tj. dobrze funkcjonować przy zmianie rozmiaru okna (operuj na width i height!). Jeżeli ktoś nie rozumie co właśnie tutaj się znajduje, to proponuję napisać program rysujący wykres funkcji kwadratowej, lub chociaż liniowej, i wypisujący miejsca zerowe, współrzędne wierzchołka itd. Aby uzupełnić wszystko, pokażę jeszcze i omówię program z poprzedniej części, czyli odbijającą się piłeczkę. Przykładowy program mógł wyglądać tak: Niektórzy czytelnicy zapewne zauważyli, że w tym kodzie piłeczka się odbija, ale jakby od jej środka. Aby to naprawić, musimy uwzględnić promień piłki: Warto spostrzec, że w pierwszym przykładzie piłka poruszała się po stałym torze, natomiast w drugim jest ona bardziej nieprzewidywalna. Możemy oczywiście uwzględnić inne siły, takie jak opór powietrza, wytracanie energii po odbiciu, a nawet zasymulowanie praw dynamiki itd. - dokładnie to robią symulacje i gry. Więc jak widać, processing jest świetną aplikacją do robienia symulacji. W następnej części skończymy rozdział poświęcony pikselom w 2D, natomiast przeniesiemy się trochę do świata 3D i spróbujemy zrobić własny syntezator. 4 Cytuj Link do komentarza Share on other sites More sharing options...
Treker (Damian Szymański) Marzec 19, 2021 Udostępnij Marzec 19, 2021 @Leoneq artykuł został właśnie zaakceptowany, więc jest już widoczny publicznie 🙂 1 Cytuj Link do komentarza Share on other sites More sharing options...
Pomocna odpowiedź
Dołącz do dyskusji, napisz odpowiedź!
Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!