Canvas, czyli programowanie grafiki w HTML

2011-07-31 0:09

Spośród wielu nowości wprowadzonych w HTML5, API do rysowania dwuwymiarowej grafiki rastrowej bez użycia wtyczek typu Flash jest z pewnością jednym z najbardziej interesujących. Wielu zresztą twierdzi (wliczając w to prominentne figury świata IT), że zwiastuje to początek końca wspomnianych pluginów. Czy rzeczywiście tak będzie, to oczywiście zobaczymy w niedalekiej przyszłości. Już teraz jednak można zobaczyć liczne przykłady na to, że technologia ta oferuje naprawdę spore możliwości.

W ramach zapoznawania się z poszczególnymi składowymi HTML5, nie mogłem więc nie przyjrzeć się bliżej elementowi <canvas> – bo to o nim oczywiście mowa. Tradycyjnym testem dla tego rodzaju rozwiązań (tj. graficznych bibliotek 2D), który zawsze staram się zaimplementować na początku, jest… chmara odbijających się piłeczek :) Nie inaczej było i w tym przypadku, czego efekty zamieszczam tutaj wraz z krótkim opisem i wprowadzeniem w podstawy Canvasa.

Gotowe demo

Twoja przeglądarka nie obsługuje elementu canvas.


Rezultat końcowy powinien być widoczny powyżej – a przynajmniej takie jest założenie :) Jeśli przykład uruchamia się poprawnie, to oprócz podziwiania trzech piłeczek, które są w nim już od razu, możemy dodawać nowe poprzez klikanie w obszar canvasa.
Robiąc to wystarczająco uparcie możemy dokonać prostej oceny efektywności renderowania w naszej przeglądarce. Wystarczy obserwować, jak zmienia się licznik FPS w miarę dodawania kolejnych obiektów. Nie obiecuję jednak, że zauważymy jakiekolwiek interesujące zmiany zanim zabawa nam się znudzi ;)

Jak to jest zrobione

Całość przykładu od strony kodu HTML zamyka się w jednym jedynym znaczniku <canvas>:

  1. <canvas width="500" height="350"></canvas>

Pod względem zachowania wobec innych elementów strony, jest on bardzo podobny do znacznika <img>, czyli zwykłego obrazka. Różnica polega oczywiście na tym, że można w nim swobodnie rysować. Trzeba tylko pamiętać o ustawieniu wymaganych atrybutów width i height na wartości w pikselach – użycie stylów CSS niestety nie wystarczy.

Narysowanie czegokolwiek wymaga już kodu przez nieco większe K, czyli JavaScriptu. Dla wygody założę, że dysponujemy biblioteką jQuery. Jeśli nie jest to prawdą, to właśnie jest doskonała okazja, żeby naprawić to niedopatrzenie ;P Biblioteka ta nie jest wprawdzie pomocna do samego rysowania po canvasie, ale znakomicie ułatwia wszystkie inne czynności, które są z tym związane.

Kontekst rysowania

Graficzne API elementu <canvas> jest w całości zamknięte w obiekt kontekstu. Jest to odpowiednik kontekstu urządzenia z Windows GDI, obiektu Graphics z GDI+ i pewnie jeszcze wielu podobnych obiektów z innych bibliotek. Wszystkie wywołania, które rysują cokolwiek albo zmieniają parametry rysowania, wykonywane są na tym właśnie obiekcie.
Kontekst pobieramy z obiektu DOM canvasa metodą getContext:

  1. var canvas = $("canvas")[0];
  2. if (canvas.getContext)
  3.     var ctx = canvas.getContext("2d");
  4. else
  5.     $(canvas).html("Element canvas nie jest obsługiwany");

…oczywiście po wcześniejszym sprawdzeniu, czy jest ona dostępna. Od razu też rozwieję nadzieje: niestety, obecnie "2d" jest jedyną pewną opcją :)

Funkcja update

Używając kontekstu możemy od razu coś narysować, ale jeśli nie chcemy prezentować tylko statycznych obrazków, to musimy jakoś zapewnić regularne wywoływanie kodu renderującego. Nie możemy oczywiście wpaść w pętlę, więc typowym rozwiązaniem jest funkcja setTimeout z odpowiednio krótkim interwałem. W HTML5 istnieje jednak teoretycznie lepsze rozwiązanie w postaci funkcji requestAnimationFrame. Ponieważ jednak z jego obsługą w różnych przeglądarkach jest różnie, trzeba sobie niestety zapewnić odpowiedni fallback:

  1. var requestAnimFrame = window.requestAnimationFrame
  2.     || window.mozRequestAnimationFrame
  3.     || window.webkitRequestAnimationFrame
  4.     || window.oRequestAnimationFrame
  5.     || window.msRequestAnimationFrame
  6.     || function(animFunc, elem) {
  7.         setTimeout(animFunc, 1000 / 60);
  8.     };
  9.  
  10. requestAnimFrame(function update () {
  11.    // (tutaj będziemy rysować)
  12.    requestAnimFrame(update, canvas);
  13. }, canvas);

W zamian możemy liczyć na wywołania zwrotne z częstotliwością ok. 60 na sekundę i – przynajmniej w teorii – optymalizację rysowania po elemencie DOM wskazanym jako drugi parametr.

Rysujemy piłki

Jak nietrudno zauważyć, piłeczki które odbijają się powyżej to po prostu wypełnione okręgi. Narysowanie takiej jakże skomplikowanej figury geometrycznej nie jest może wielkim wyzwaniem, ale wymaga zastosowania kilku koncepcji z API elementu <canvas>.
I tak zaczynamy od ustawienia stylu wypełnienia na jednolity kolor fioletowy:

  1. ctx.fillStyle = "purple";

Styl ten może być nie tylko dowolnym kolorem (w formacie CSS, czyli np. #ffffff lub rgba(...)), ale także gradientem lub powtarzalnym wzorem złożonym z kopii obrazka. Przypomina więc to np. obiekt typu brush z GDI.
Samo rysowanie kółek jest trochę bardziej złożone niż można by się spodziewać, bo wymaga w sumie trzech wywołań:

  1. for (var i = 0; i < balls.length; ++i) {
  2.     ctx.beginPath();
  3.     ctx.arc(ball.x, ball.y, BALL_RADIUS, 0, 2 * Math.PI);
  4.     ctx.fill();
  5. }[/javascript]
  6. Jest tak dlatego, że kształty na <em>canvasie</em> opisujemy w formie <strong>ścieżek</strong> (<em>paths</em>), które potem wypełniamy (metodą <code>fill</code>) lub obrysowujemy (<code>stroke</code>). Kształt każdej piłki to nowa ścieżka (<code>beginPath</code>), składająca się z jednego łuku okręgu (<code>arc</code>), który opisujemy pozycją środka, promieniem oraz zakresem kątów. Nas oczywiście interesuje pełne koło, więc ów zakres ustalamy na [tex][0;2\pi][/tex].
  7.  
  8. <h3>Trochę tekstu</h3>
  9. Poza samymi piłkami na ekranie widoczna jest też ich ilość oraz, naturalnie, licznik FPS-ów :) Wartość dla tego ostatniego liczymy dokładnie tak samo, jak w każdej innej sytuacji, tj. pobierając w każdej klatce aktualny czas (tutaj <a href="http://api.jquery.com/jQuery.now/">funkcją <code>now</code> z jQuery</a>) i odejmując od niego poprzedni rezultat, a następnie uśredniając wynik:
  10. [javascript]var lastTime = $.now();
  11. var fps = 0, framesCount = 0, frameTime = 0;
  12. requestAnimFrame(function update () {
  13.     var time = $.now();
  14.     var dt = (time - lastTime) / 1000;
  15.     lastTime = time;
  16.  
  17.     frameTime += dt; framesCount++;
  18.     if (frameTime >= 1.0) {
  19.         fps = Math.floor(framesCount / frameTime);
  20.         frameTime = framesCount = 0;
  21.     }
  22.     // ...
  23.     requestAnimFrame(update, canvas);
  24. }, canvas);

Komu powyższy kod nie wydaje się do końca zrozumiały (przy wzięciu poprawki na język programowania, rzecz jasna ;]), polecam chociażby mój artykuł na temat pętli czasu rzeczywistego, gdzie powyższa konstrukcja jest dokładnie opisana.
Tutaj zajmiemy się natomiast wypisaniem obliczonej wartości FPS oraz aktualnej ilości piłek. Nie jest to bynajmniej trudne:

  1. ctx.strokeStyle = "black";
  2. ctx.textAlign = "left";
  3. ctx.strokeText("Balls: " + balls.length, 0, 10);
  4. ctx.strokeText("FPS: " + fps, 0, 25);

Tym razem ustawiamy styl obrysowania i wyrównywanie tekstu względem pozycji rysowania. Tekst wypisujemy funkcją strokeText, przyjmującą też współrzędne X i Y.

Reszta…

To w zasadzie tyle, jeśli chodzi o samo rysowanie po canvasie. Do poprawnego działania przykład potrzebuje oczywiście jeszcze kilku innych rzeczy, takich jak choćby implementacja poruszania się i odbijania piłek oraz obsługę kliknięć. Ponieważ jednak nie dotyczą one bezpośrednio API graficznego, nie będę im poświęcał miejsca w tym i tak już zdecydowanie przydługim wpisie. Można się im oczywiście przyjrzeć, czytając pełny kod przykładu działającego wyżej.

Jeśli zaś ktoś chciałby dalszych informacji na temat możliwości i wykorzystania elementu <canvas>, to dobrym punktem wyjścia jest tutorial na stronach Mozilli. Zawsze też można odwołać bezpośrednio do standardu :)



12 comments for post “Canvas, czyli programowanie grafiki w HTML”.
  1. MSM:
    July 31st, 2011 o 2:10

    U mnie FPS… wzrasta razem z dodawaniem piłeczek o_o. Podskoczył z 55 do 71 po kliknięciu 100 razy.

    Tak czy inaczej dzięki, wiadomo przynajmniej co będzie za 10 lat na stronach w oczy walić zamiast flasha (bo js nie jest na razie w kręgu moich zainteresowań) ;)

  2. Kacper Kołodziej:
    July 31st, 2011 o 11:18

    Bardzo przydatny artykuł. Jestem właśnie zainteresowany HTML5 ponieważ potrzebuję wykonać edytor nut, który działałby online. A ponieważ pojawił się HTML5, to porzuciłem pomysł robienia tego we flashu.

  3. adik:
    July 31st, 2011 o 11:46

    U mnie wraz z dodawaniem FPS spadaja z 50 do 22 po dodaniu 300 pileczek xD

  4. syriusz:
    July 31st, 2011 o 21:35

    Niezła wydajność, 1000 piłeczek redukuje FPS 2x. Szkoda że nie ma podobnej aplikacji we flash’u do porównania…

  5. Kos:
    August 1st, 2011 o 10:37

    Na chrome zdaje się chodzić z dobrymi FPS-ami, ale o vsync możemy sobie pomarzyć. :D Sad.

    Ciekawe, czy ten canvas doczeka się akceleracji przez OpenVG (zwłaszcza na platformach mobilnych). Wtedy by to już było COŚ.

  6. kapec94:
    August 1st, 2011 o 16:23

    Cóż, myślę, że sprzętowa akceleracja canvasa jest tylko i wyłącznie kwestią czasu. ;)

  7. Zielony:
    August 3rd, 2011 o 19:31

    Przy 3142 piłeczkach mam 10 FPS. ;]

  8. Xion:
    August 3rd, 2011 o 21:57

    @kapec94: Miejmy nadzieję. Z GDI nie było tak różowo :)

    @Zielony: Iii tam, ja przy ~12 tysiącach miałem 6-7 :)

  9. Janek566:
    August 4th, 2011 o 18:36

    Hmmm prezetujesz tutaj standard ktory jeszcze nie zostal wydany? Pisze wlaznie z fona, sgs2 I jakos przegladarka wyswietla mi twoj canvas. Moze chodzi o sam webkit na ktorym przegladarka jest oparta? Mimo wszystko dziala fajnie widziec nowy standard w akcji. Pozdrawiam.

  10. redmer:
    August 5th, 2011 o 0:28

    Od razu jak zobaczypem to musialem sprawdzic jak to na komorce wygląda i o dziwo działa ,.trochę przycina ale i tak jestem pod wrażeniem

  11. as:
    August 12th, 2011 o 12:43

    U mnie 300 pileczek nie zmienilo fps dalej mi sie nie chce :)

Comments are disabled.
 


© 2017 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.