Mazakiem po WWW czyli kilka słów o obiekcie «canvas» Robert BoBsoN Partyka Streszczenie: Rozwój informatyki to ciągły bieg za nowymi technologiami. Tendencja ta nie omija także globalnej pajęczyny stron WWW. Czasy, gdy serwisy internetowe składały się tylko z tekstu i hiperłączy minęły bezpowrotnie. Standardy, które wytyczają drogę rozbudowy przeglądarek, rozszerzane są o kolejne pomysły organizacji takich jak W3C, WHATWG oraz firm takich jak Nokia, Google czy Microsoft. Najnowsze wydania Firefox i Opery wprowadziły natywną obsługę strumieni video i dźwięku. W najbliższym czasie spodziewać się należy kolejnego przełomu w postrzeganiu narzędzia jakim jest WWW. Przyjrzyjmy się jednemu z elementów HTML5, które mogą przyczynić się do tego przełomu. Czym właściwie jest <canvas>? Z obiektami o działaniu podobnym do <canvas> spotkał się każdy, kto próbował swych sił w programowaniu w C/C++ czy Pascal (Delphi). Jest to nic innego jak płótno na ekranie, na której możemy narysować dowolną grafikę. Nic nadzwyczajnego, ale gdy dodamy do tego szereg wbudowanych funkcjonalności, otrzymujemy elastyczne i przydatne narzędzie, dzięki któremu możemy zmienić odbiór naszych stron WWW. <canvas> po raz pierwszy został wprowadzony przez firmę Apple dla Mac OS X Dashboard a następnie zaimplementowany w Safari. Implementacja tego obiektu została wykonana także w silniku Gecko, począwszy od wersji 1.8 oraz w przeglądarce Opera w związku z zawarciem tego obiektu w specyfikacji WHATWG Web application 1.0 znanej jako HTML5. Nie jest zaskakującym fakt, że firma Microsoft nie implementowała tego obiektu <canvas> w IE zawdzięczamy Google.
36 Robert BoBsoN Partyka Podstawy Tak, jak każdą zabawę z elementami HTML i tutaj zaczynamy od wstawienia obiektu w kod strony WWW. Przykładowo: <canvas id="mycanvas" width="400" height="300"> </canvas> wstawi obiekt o wymiarach 400 na 300 pikseli. Zaleca się stosowanie zamykającego tagu </canvas> ze względu na nieprawidłowe interpretowanie <canvas.../> przez niektóre przeglądarki. Tego typu konstrukcja pozwala także na zastosowanie kodu, który zostanie wyświetlony w przeglądarkach nie wspierających tego obiektu: <canvas id="mycanvas" width="400" height="300"> Twoja przeglądarka nie wspiera obiektu Canvas. </canvas> W efekcie interpretacji przez przeglądarkę takiego kodu naszym oczom ukaże się zachęcająca do dalszej pracy pustka. Mając już obiekt <canvas> w drzewie DOM strony, przystępujemy do manipulacji na tym obiekcie. W tym celu wykorzystujemy JavaScript. <canvas> obsługuje zdarzenia HTML, dzięki czemu możliwe jest oprogramowanie interakcji z użytkownikiem. Do rysowania obrazu wykorzystywane są konteksty rysowania. Może być ich więcej niż jeden, dzięki czemu zyskujemy dodatkowe możliwości, takie jak dostęp do OpenGL ES z poziomu JavaScript (o czym za chwilę). Podstawowym kontekstem jest 2d. var canvas = document.getelementbyid( MyCanvas ); var ctx = canvas.getcontext( 2d ); Po pobraniu kontekstu możemy przystąpić do rysowania na stworzonym przez nas płótnie, wykorzystując dostarczane przez obiekt metody. canvas obsługuje jedynie prostokąt jako wbudowany kształt. fillrect(x,y,szerokość,wysokość) rysuje wypełniony prostokąt, strokerect(x,y,width,height) rysuje obrys prostokąta, a clearrect(x,y,width,height) czyści dany obszar, czyniąc go całkowicie przeźroczystym Każda inna figura musi być zbudowana ze ścieżek. Tych na szczęście jest kilka rodzajów. Rysowanie figur przy użyciu ścieżek jest trochę bardziej skomplikowane wymaga wykorzystania więcej niż jednej metody. Dodatkowo ważnym elementem staje się kontrola nad położeniem pędzla. Ścieżkę rozpoczynamy od wydania beginpath(). Kolejne metody przesuwają i rysują pędzlem. Ostatnia zamyka ścieżkę i aktywuje jej wykonanie. Jest to albo:
Mazakiem po WWW czyli kilka słów o obiekcie «canvas» 37 fill() dla figur wypełnionych lub stroke() dla obrysów figur. Opcjonalna metoda closepath() służy do narysowania linii prostej z bieżącej pozycji pędzla do punktu początkowego ścieżki. Pierwszą metodą wywoływaną po beginpatch() w większości przypadków będzie moveto(x,y). Służy ona do przeniesienia pędzla bez pozostawiania śladu na płótnie w wybraną pozycję. W celu rysowania śladu ścieżki otrzymujemy całkiem pokaźny zestaw metod: lineto(x, y) rysuje linię prostą z aktualnej pozycji do podanej, arc(x, y, r, początkowykąt, końcowykąt, przeciwniedozegara) rysuje okrąg lub fragment okręgu od początkowego do końcowego końca, zgodnie lub przeciwnie do ruchu wskazówek zegara o promieniu r i środku w punkcie x,y. quadraticcurveto(cp1x, cp1y, x, y) oraz beziercurveto(cp1x, cp1y, cp2x, cp2y, x, y) służy do rysowania krzywych, oraz: rect(x,y, szerokość, wysokość) dodająca obrys prostokąta do aktualnej ścieżki. Pamiętać należy, że pozycją pędzla po wykonaniu którejkolwiek z tych metod, będzie pozycja ostatniego piksla zaplanowanego do narysowania. Wiemy już, jak poruszać pędzlem. Kolejny krok to dobór farb. Do dyspozycji mamy dwie własności obiektu kontekstu: fillstyle określa kolor wypełnienia, zaś: strokestyle określa kolor obrysu. Kolory te zapisane mogą być w jednym z czterech formatów: 1. słowna nazwa zgodna z nazewnictwem kolorów w CSS, 2. zapis #RRGGBB, 3. zapis z wykorzystaniem funkcji rgb(r,g,b) lub funkcji rgba(r,g,b,a) przykładowo: ctx.fillstyle = "orange"; ctx.fillstyle = "#FFA500"; ctx.fillstyle = "rgb(255,165,0)"; ctx.fillstyle = "rgba(255,165,0,1)"; Innymi słowy, jest to zapis zgodny ze specyfikacją zapisów kolorów w standardzie CSS3. Przeźroczystość (lub jak kto woli siłę nacisku) pędzla możemy określić przy pomocy globalnej własności globalalpha. Przyjmuje ona wartości zmiennoprzecinkowe z przedziału 0 do 1.
38 Robert BoBsoN Partyka Sposób rysowania pędzla po płótnie określają kolejne cztery własności: linewidth określająca szerokość linii rysowanej przez pędzel, linecap określająca sposób rozpoczęcia i zakończenia rysowanej linii (możliwe wartości to: butt, round i square), linejoin określa sposób, w jaki będą łączone kolejne elementy ścieżki rysowanej przez pędzel (możliwe wartości to: round, bevel i miter), oraz miterlimit określająca, jak daleko mogą znajdować się od siebie punkty połączenia rysowanych linii (dotyczy linii szerszych niż 1px). Nie samymi kreskami i krzywymi jednak obraz żyje. Czasami przydatne jest wykorzystanie grafiki stworzonej np. w GiMP-ie. Jest to możliwe dzięki obiektowi Image() i metodzie drawimage(). Pierwszym krokiem jest załadowanie obrazka z pliku: var img = new Image(); img.src = jesien2009.png ; lub przez zakodowany tekst: img.src = data:image/gif;base64,r0lgo... trochę base64... Ow== ; Do umieszczenia załadowanego obrazka na obszarze płótna obiektu <canvas> używamy metody drawimage(). Występuje ona w trzech wersjach: drawimage(img, x, y) kopiuje obrazek w obszar o początkowej pozycji x,y na obiekcie <canvas>, drawimage(image, x, y, szerokość, wysokość) kopiuje obrazek w obszar o początkowej pozycji x,y z równoczesnym przeskalowaniem do nowych wymiarów, oraz drawimage(image, sx, sy, sszerokość, swysokość, dx, dy, dszerokość, dwysokość) kopiuje fragment obrazka od pozycji sx,sy i wymiarach zdanych w dwóch kolejnych parametrach na pozycję dx,dy w obszarze płótna obiektu canvas z równoczesnym przeskalowaniem do nowych wymiarów podanych w kolejnych dwóch parametrach. Oprócz prostego przerysowywania obrazka na płaszczyznę <canvas>, istnieje możliwość użycia go jako wzoru dla wypełnienia. Umożliwia to metoda createpattern(), która przyjmuje dwa parametry: obiekt obrazka jako pierwszy i określenie sposobu powtarzania wzorca (repeat, repeat-x, repeat-y, no-repeat). W chili obecnej wspierane jest tylko repeat. var img = new Image(); img.src = fajne_wypelnienie.png ; img.onload = function(){
Mazakiem po WWW czyli kilka słów o obiekcie «canvas» 39 } var ptrn = ctx.createpattern(img, repeat ); ctx.fillstyle = ptrn; ctx.fillrect(0,0,150,150); Tryb kopiowania obrazu lub rysowania po nim na zasadzie dokładnego przykrywania nowym wzorcem, nie zawsze jest tym, czego potrzebujemy. Kolejny raz z pomocą przychodzą własności obiektu kontekstu. globalcompositeoperation pozwala określić, w jaki sposób łączone ze sobą będą źródło i elementy istniejące już na płótnie. Wtym miejscu otwiera się bogactwo możliwości wyboru: source-over domyślny tryb, w którym źródło przykrywa dokładnie piksle miejsca przeznaczenia; destination-over tryb, w którym nowe elementy umieszczane są poniżej istniejącego obrazu widoczne tylko w miejscach przeźroczystości aktualnego obrazu; source-in oraz destination-in tryb części wspólnej; na płaszczyźnie umieszczony zostanie obszar wspólny pochodzący z źródła lub płótna zależnie od wybranego trybu; source-out oraz destination-out tryby wycięcia, na płaszczyźnie umieszczona zostanie albo część źródła nie pokrywająca elementów w miejscu przeznaczenia, albo część elementów na płaszczyźnie nie przykryta przez element źródła (część wspólna zostanie zamieniona na przeźroczystość); source-atop oraz destination-atop tryby wycięcia z równoczesnym połączeniem części wspólnych. W pierwszym przypadku tylko fragment źródła pokrywający się z elementami w miejscu przeznaczenia będzie pozostawiony i narysowany, w drugim przypadku pozostawiona zostanie tylko część elementów z miejsca przeznaczenia, która pokrywa się z źródłem (źródło umieszczane jest pod spodem ); lighter w części wspólnej nastąpi zsumowanie kolorów źródła i elementów w miejscu przeznaczenia; darker w części wspólnej nastąpi odjęcie wartości koloru źródła od koloru przeznaczenia; xor część wspólna źródła i elementów w miejscu przeznaczenia zostanie usunięta; copy tylko źródło zostanie narysowane, wszystkie inne elementy z zadanego obszaru zostaną usunięte. Oprócz trybów rysowania, istnieje możliwość zdefiniowania ścieżki przycięcia obrazu na płaszczyźnie płótna. Oznacza to, że wszystko, co znajduje się poza obszarem wyznaczonym przez ścieżkę, zostanie usunięte. Ścieżkę taką deklarujemy w identyczny
40 Robert BoBsoN Partyka sposób, jak ścieżkę elementu rysowanego na powierzchni obiektu canvas zmienia się jedynie metoda zamykająca: ctx.beginpath(); ctx.arc(0,0,60,0,math.pi*2,true); ctx.clip(); Transformacje Możliwości obiektu <canvas> nie kończą się na rysowaniu ścieżek, czy też kopiowaniu obrazków. Kolejny zestaw metod dodaje obsługę transformacji. O ile opisywane wcześniej metody odnosiły się do sterowania pędzlem, nakładania obrazków ogólnie rysowania po płótnie transformacje odnoszą się do samej powierzchni tworzonego obrazu. Nie należy mylić tego z manipulacją istniejącym obrazem. Transformacje zmieniają tylko układ siatki punktów poprzez przemieszczenie jej punktu zerowego, obrót, zmianę skalowania bez uszczerbku dla już istniejących fragmentów obrazu. Skomplikowane? Spójrzmy na przykład: Na płaszczyźnie umieśćmy kwadrat o boku 20px w pozycji (10,10). ctx.strokerect(10,10,20,20); Następnie narzućmy translację: ctx.translate(100, 100); Komenda ta przenosi punkt zerowy siatki odniesienia w nowe koordynaty. Jak widzimy już narysowany prostokąt pozostał nietknięty. Narysujmy go jeszcze raz dla pewności. ctx.strokerect(10,10,20,20); Prostokąt pojawił się... w nowym miejscu. Tak właśnie działają transformacje. Sposób widzenia płaszczyzny bez uszczerbku dla już narysowanych elementów. Sprawdźmy kolejne metody. ctx.scale(0.5, 0.5); ctx.strokerect(10,10,20,20); Odpowiada za przeskalowanie płaszczyzny. Parametry odpowiednio dla osi X i Y przyjmują wartości zmiennoprzecinkowe równe lub większe niż zero. Wartości z przedziału 0 do 1 oznaczają zgniatanie obrazu, większe od jedynki jego rozciąganie. Jak widzimy narysowany prostokąt jest połowę mniejszy, a dodatkowym efektem jest zmiana jego pozycji. To dlatego, że przeskalowaliśmy siatkę punktów odniesienia obiektu a nie sposób rysowania obiektu. Kolejna metoda pozwoli na obrócenie siatki o zadany w radianach kąt zgodnie z ruchem wskazówek zegara.
Mazakiem po WWW czyli kilka słów o obiekcie «canvas» 41 ctx.rotate(math.pi*0.25); ctx.strokerect(10,10,20,20); Istnieje jeszcze dwie metody, które pozwalają na manipulację bezpośrednio na macierzy przekształceń. transform(m 11, m 12, m 21, m 22, dx, dy) mnożąca aktualną macierz przekształceń przez macierz: m 11 m 21 dx m 12 m 22 dy 0 oraz 0 1 settransform(m 11, m 12, m 21, m 22, dx, dy) która ustawia nowe wartości macierzy przekształceń na: m 11 m 21 dx m 12 m 22 dy 0 0 1 Gdzie można wykorzystać transformacje? Głównie przy rysowaniu skomplikowanych elementów, takich jak np. hipotrochoida czy epicykloida. Dodawanie wyliczeń transformacji do funkcji rysującej taki element zwiększałoby jej złożoność i drastycznie zmniejszało czytelność kodu. Dzięki transformacjom siatki odniesienia obiektu <canvas> możemy tego uniknąć. Narzucanie kolejnych transformacji wiąże się z ryzykiem utraty panowania nad aktualnym stanem siatki odniesienia. Dodatkowym czynnikiem chaosu jest zmiana barw, sposobu łączenia elementów, wartości opisujących pędzel. W złożonym programie łatwo doprowadzić do sytuacji, w której przez drobną pomyłkę, część obrazu ląduje poza krawędziami obiektu <canvas>. Aby temu zapobiec, stosujemy metodę save(), która zapisuje aktualny stan obiektu <canvas> na stos. Stan przywracamy metodą restore(). Jako, że save() zapamiętuje informacje na stosie możliwe jest zapamiętanie kilku kolejnych stanów, a następnie cofanie się po nich do stanu pierwotnego. Rysowanie tekstu Obiekt <canvas> nie tylko pozwala na rysowanie obrazu. Kontekst 2d posiada także funkcje odpowiedzialne za narzucanie tekstu na obraz. Najprostszą metodą rysowania tekstu jest filltext(), która przyjmuje następujące parametry: tekst do wyświetlenia, koordynaty kolejno x i y początku rysowania tekstu, oraz opcjonalnie maksymalną szerokość (w tym przypadku silnik renderujący będzie starał się użyć fontu o większej gęstości lub mniejszej czcionce w przypadku, gdy treść nie mieści się w zadanej szerokości). measuretext() zwraca informację o szerokości zadanego tekstu w pikslach, mozpathtext() dodaje obrys tekstu do aktualnie przygotowywanej ścieżki. Pozwala to na wrysowanie w obraz tekstu, który będzie posiadał obrys. Najciekawszą z metod rysujących tekst wydaje się moztextalongpath(), która wpisuje tekst wzdłuż utworzonej ścieżki. Sama grafika nie jest skalowana lub poddawana
42 Robert BoBsoN Partyka Rys 1 Praktyczny przykład (Pełen opis znajdziesz pod adresem: https://developer.mozilla.org/en/manipulating_video_using_canvas) zniekształceniom. Proces polega na traktowaniu ścieżki jako linii bazowej pod daną literą. Pozwala to na uzyskanie ciekawych efektów, takich jak pofalowany czy spiralny tekst. Możliwe też jest ułożenie tekstu wzdłuż ścieżki utworzonej z... tekstu. Sterowanie stylem rysowanego tekstu, podobnie jak w poprzednich przypadkach, odbywa się poprzez globalne własności obiektu odpowiadającego za kontekst. font określa rozmiar i nazwę aktualnie używanej czcionki, zgodnie ze standardem zapisu wykorzystywanym w CSS, textalign definiuje sposób ułożenia tekstu względem np. ścieżki; możliwe wartości to left, right, center, start oraz end (domyślnie: start), textbaseline ustala położenie linii bazowej tekstu; aktualnie wspierane wartości to: top, hanging, middle, alphabetic, bottom (domyślnie: alphabetic). Praktyczne wykorzystane obiektu <canvas> można przedstawić implementując blue box dla obrazu video. Przypomnijmy czym jest efekt blue box jest to zastąpienie wybranego koloru tła obrazem pochodzącym z innego źródła. W efekcie uzyskujemy złudzenie przebywania osoby w innym miejscu niż jest faktycznie. Efekt ten znany jest także pod nazwami: bluescreen, greenscreen czy też chroma key. Jedyne co potrzebujemy, to obiekt <video>, z którego pobierać będziemy kolejne klatki oraz dwa obiekty <canvas>. W drugim z tych obiektów ustawiamy tło na obrazek, który ma być tłem sceny. Algorytm działania jest prosty. Po załadowaniu strony, zdarzeniem onload inicjujemy wszystkie potrzebne nam zmienne oraz inicjujemy przeliczanie widocznych klatek w funkcji settimeout(). Funkcja uruchomiona przez settimeout() każdorazowo na koniec swojego działania ustawia ponowne wywołanie samej siebie z opóźnieniem 0. W praktyce powinno ustawiać się opóźnienie, wyliczając czas na podstawie framerate filmu. Skupimy się na obliczeniach efektu chroma key. W tym celu najpierw kopiujemy zawartość obrazu z <video> do pierwszego z posiadanych obiektów <canvas>. ctx1.drawimage(myvideo, 0, 0, mywidth, myheight);
Mazakiem po WWW czyli kilka słów o obiekcie «canvas» 43 Następnie pobieramy macierz obrazu (dane o kolorach w każdym pikslu w postaci tablicy) let frame = ctx1.getimagedata(0, 0, mywidth, myheight); let l = frame.data.length / 4; Przeliczamy w iteracji wszystkie piksle jeżeli kolor któregoś z nich zgadza się z określonym przez nas tłem ustawiamy jego alpha na zero. for (let i = 0; i < l; i++) { let r = frame.data[i * 4 + 0]; let g = frame.data[i * 4 + 1]; let b = frame.data[i * 4 + 2]; if (g > 100 && r > 100 && b < 43) frame.data[i * 4 + 3] = 0; } W ostatnim kroku umieszczamy przetworzoną macierz obrazu w drugim obiekcie <canvas>: ctx2.putimagedata(frame, 0, 0); Powyższy przykład umieszcza obraz video na statycznym tle. Przy niewielkiej modyfikacji (kopiowanie z dwóch źródeł, narzucanie na trzeci obiekt <canvas>) możliwe jest uzyskanie efektu nałożenia dwóch obrazów video. Kontekst 3D Peter Drucker powiedział kiedyś, że najlepszą metodą przewidywania przyszłości jest jej tworzenie. Nie wiadomo, czy ta sentencja przyświecała Vladimirowi Vukićevićowi, gdy rozpoczynał prace nad kontekstem 3d. Jej celem było wprowadzenie możliwości wykorzystania OpenGL ES (z sprzętową akceleracją) poprzez komendy JavaScript dostępne w ramach kontekstu graficznego obiektu <canvas>. W chwili obecnej dostępne jest rozszerzenie dla Firefox 3.5, które wprowadza dostęp do kontekstów 3d. Jego prace nie zostały bez echa. Inna grupa ludzi rozpoczęła prace nad biblioteką JS, która miała ułatwić tworzenie aplikacji w trzecim wymiarze, czego efektem jest C3D Library. Biblioteka ta wprowadza szereg udogodnień, takich jak obsługa scen, światła, system cząsteczek, kamerę czy też możliwość importu modeli z kilku formatów plików. 25 marca podczas Game Developers Conference w San Francisco fundacja Mozilla oraz grupa Khronos ogłosiły podjęcie wspólnych działań na rzecz wprowadzenia akceleracji 3D do przeglądarek internetowych. Wsparcie dla projektu zadeklarowało także Google co wraz z istnieniem przeglądarki Chrome wydaje się przesądzać o przyszłym sukcesie tego projektu. Pozostaje jedynie uzbroić się w cierpliwość i testować kolejne wersje rozszerzenia Canvas:3D.
44 Robert BoBsoN Partyka Powyższy artykuł nie wyczerpuje wszystkich zagadnień związanych z obiektem <canvas>. Mam nadzieję, że udało mi się zachęcić was do bliższego przyjrzenia się temu elementowi. Czas pokaże, czy czeka nas wysyp graficznych aplikacji on-line. Źródła: https://developer.mozilla.org https://developer.mozilla.org/en/canvas_tutorial/transformations http://www.whatwg.org/specs/web-apps/current-work/ https://wiki.mozilla.org/canvas:3d http://www.c3dl.org/ https://labs.mozilla.com/forum/?categoryid=9 http://people.mozilla.org/~vladimir/canvas3d/ http://groups.google.com/group/canvas-3d https://developer.mozilla.org/en/manipulating_video_using_canvas