Obrazki w canvas

Rysowanie prostych kształtów w poznany sposób nie jest problemem. Jednak przy bardziej skomplikowanych grafikach o wiele lepiej jest przygotować obrazek w jakimś programie graficznym i wczytać go na nasze płótno. Zewnętrzne obrazki mogą przyjmować standardowe rozszerzenia czyli GIF, JPEG i PNG. Osobiście polecam ten ostatni format - ze względu na obsługę 255 poziomowej poziomowej przezroczystość.

Aby narysować obrazek na płótnie musimy wykonać 2 kroki:

  • Przygotować obrazek dla Javascript. Już to poznaliśmy w rozdziale o grafikach.
  • Obrazek pod postacią zmiennej rysujemy na płótnie korzystając z metody drawImage()

Podstawowy wariant użycia metody drawImage(image, x, y) przyjmuje następujące atrybuty:
image - określa obrazek, który będziemy rysować na płótnie,
x, y - określają położenie rysowanego obrazka na płótnie.

Aby mieć pewność, że rysowany obrazek jest już w pełni załadowany skorzystamy dla niego ze zdarzenia onload.

Koniec teorii - czas na praktykę


var image = new Image();
image.onload = function(){
    // Rysujemy
}
image.src = 'super_grafika.png';

Zauważ kolejność deklaracji zdarzenia onload i ustawiania parametru src.
Parametr src powinien być ustawiany PO ustawieniu zdarzenia onload. Czemu? Wskazanie src powoduje, że przeglądarka zaczyna wczytywać daną grafikę. Jeżeli obrazek znajduje się w cache przeglądarki, wtedy jest załadowany natychmiastowo. Tak więc obrazek zostaje załadowany, ale nasz skrypt nie ustawił jeszcze przypisanego zdarzenia onload.

W przeglądarkach pracujących na silniku Webkit (np Chrome) możemy pobrać wymiary obrazka dopiero po całkowitym jego wczytaniu (czyli dopiero po wykonaniu darzenia onload) - dlatego warto się trzymać powyższej kolejności.

Przejdźmy teraz do przykładu i narysujmy wykres:


var c = document.getElementById('canvasBar').getContext('2d');

var bg = new Image();
bg.onload = function(){

    //rysujemy wczytaną grafikę z tłem
    c.drawImage(bg,0,0);

    for (var i=0; i<8; i++) {

        //słupki zaczynamy rysować od 30, każdy kolejny słupek rysujemy co 80px,
        //a od wyliczonego punktu odejmujemy połowę słupka czyli 10
        var xBar = 30 + (i*80) - 10;

        c.fillStyle = "rgba(193, 228, 252, 1)";    //ustawiamy kolor słupka

        //słupek rysujemy od pozycji
        //x = x_slupka,
        //y = 115
        //rysujemy w górę - dlatego wysokość słupka (ostatni parametr) jest ujemna (-70)

        c.fillRect(xBar, 115, 20, -70);
    }
};

bg.src = 'background.gif';
Twoja przeglądarka nie obsługuje canvas

Oczywiście w tak prostej formie nasz skrypt raczej nie będzie przydatny. Dodaj my więc do niego jakieś sensowne dane i wyrysujmy je:


var c = document.getElementById('canvasBar').getContext('2d');

//sensowne dane do rysowania :)
var data = [100, 80, 70, 20, 10, 50]; //dane które narysujemy

var widthChars = 560; //nie chcemy zajmować całej powierzchni obrazka
var fromX = 600 - widthChars; //pierwszy słupek rysujemy od jakiegoś miejsca
var step = Math.round(560 / data.length)-10; //co ile będziemy rysować kolejny słupek
        
var bg = new Image();

//ustawiamy zdarzenie onload
bg.onload = function(){

    c.drawImage(bg,0,0);

    //prosta pętla po tablicy danych
    for (var i=0; i<data.length; i++) {
        //cien słupka
        var xBar = fromX + (i*step) - 20; //20 to polowa szerokości słupka.

        //cień dla słupków - zwykły słupek nieco przesunięty
        c.fillStyle = "rgba(0, 0, 0, 0.3)"; //ustawiamy kolor dla cienia
        c.fillRect(xBar, 115, 45, -data[i]); //rysujemy słupek cienia

        c.fillStyle = "rgba(193, 228, 252, 1)";    //ustawiamy kolor słupka
        c.fillRect(xBar, 115, 40, -data[i]); //rysujemy słupek
    }

}

bg.src = 'background.gif';
Twoja przeglądarka nie obsługuje canvas

Równie dobrze możemy zastosować inne receptury do rozmieszczania takich słupków. Dodatkowo możemy upiększyć nasz wykres poprzez dodanie innych kolorów - trzymanych np w dodatkowej tablicy. Poniżej zamieszczam przykładową implementację tego rozwiązania. Największym problemem jest tutaj rzutowanie danej wartości na tablicę kolorów, która przecież długością wcale nie musi się pokrywać z tablicą danych.


var c = document.getElementById('canvasBarsColor').getContext('2d');
var data = [100,80,70,20,10,50,30,90,43];
var max = 100; //dla ułatwienia maksymalną wartość ustawiamy z palca
var min = 10; //to samo dla min

var colors = ['#d31111','#f55625','#f5bd25','#f5e925','#c6f525'];

//dana wielkość musi się zawierać w jakimś przedziale - np 0-25 - to pierwszy kolor, 25-50 - drugi kolor itp.
var section = Math.floor(max / colors.length);
var widthChars = 560;
var fromX = 600 - 560;
var step = Math.round(560 / data.length)-10;
        
var bg = new Image();
bg.onload = function(){

    c.drawImage(bg, 0, 0);

    for (var i=0; i<data.length; i++) {
        var xBar = fromX + i*step - 20;
        c.fillStyle = "rgba(0, 0, 0, 0.3)";
        c.fillRect(xBar, 115, 45, -data[i]);

        c.fillStyle = colors[ Math.floor((data[i]-min) / section) ];
        c.fillRect(xBar, 115, 40, -data[i]);
    }
};
bg.src = 'background.gif';
Twoja przeglądarka nie obsługuje canvas

Skalowanie grafiki

Kolejny wariant użycia metody drawImage ma następującą postać:

drawImage(image, x, y, width, height) - pierwsze atrybuty już znamy. Dodatkowe width i height określają nowe rozmiary rysowanego obrazka. Możemy je wykorzystać do skalowania obrazka.

Grafika bez przekształceń:

domek


var c = document.getElementById('canvasScale').getContext('2d');

var house = new Image();        
house.onload = function(){
    //wczytujemy tło
    for (var x=0; x<4; x++) {
        for (var y=0; y<4; y++) {
            c.drawImage(house, x*40, y*40, 40, 40);
        }
    }
    c.drawImage(house, 40, 40, 80, 80);
};
house.src = 'domek.jpg';

Po przekształceniach i narysowaniu na płótnie:

Twoja przeglądarka nie obsługuje canvas

Wycinanie kawałka grafiki

Ostatni wariant metody drawImage służy do wycinania kawałka grafiki i rysowaniu go na płótnie.

Wariant ten ma postać:

drawImage(image, sx, sy, swidth, sheight, dx, dy, dWidth, dHeight): atrybut image znamy. Atrybuty sx, sy, swidth, sheight określają położenie oraz rozmiar obszaru do wycięcia na obrazku źródłowym. Atrybuty dx, dy, dWidth, dHeight określają położenie oraz rozmiar wyciętego wycinka nałożonego na canvas. Najlepiej zilustruje to schemat:

crop canvas

Grafika bez przekształceń:

Aniołek

Wycinamy pani twarzyczkę i wrzucamy na płótno:


var c = document.getElementById('canvasCroop').getContext('2d');
var angel = new Image();
angel.onload = function(){
    c.drawImage(angel, 78, 31, 80, 80, 10, 10, 160, 160);
};
angel.src = 'aniolek.jpg';
Twoja przeglądarka nie obsługuje canvas

CSI niech się od nas uczy :)

Manipulacja pikselami

Jeżeli popatrzymy na nasze płótno dojdziemy do wniosku, że jest to w zasadzie uporządkowany zbiór pikseli. Lewy górny róg to początek, a dolny prawy do koniec.
I tak jest w rzeczy samej!
Aby manipulować poszczególnymi pikselami wykorzystamy do tego obiekt typu ImageData. Obiekt taki jest "zapisem grafiki" i zawiera 3 właściwości: width, height i data. Width i height to nic innego jak wymiary "grafiki". Data zawiera tablicę danych o poszczególnych pikslach.

Element canvas udostępnia nam metody, dzięki którym możemy obsłużyć piksele:

  • naszImageData = context.createImageData(sw, sh) - tworzy PUSTY obiekt typu ImageData o wymiarach podanych w atrybutach. Wszystkie piksele zwróconego obiektu są przezroczyste
  • naszImageData = context.createImageData(innyImageData) - zwraca obiekt typu ImageData o wymiarach takich samych jakie ma obiekt przekazany w atrybucie (wszystkie piksele są przezroczyste)
  • naszImageData = context.getImageData(x, y, w, h) - pobiera obiekt typu ImageData, który jest wycinkiem canvasu o wymiarach podanych w atrybutach. Jeżeli x i y nie są podane, wtedy przyjmują wartości 0
  • naszImageData.width - szerokość obiektu ImageData
  • naszImageData.height - wysokość obiektu ImageData
  • naszImageData.data - zwraca 1 wymiarową tablicę, której poszczególnymi wartościami są składowe RGBa pikseli czyli
    naszImageData.data =[r,g,b,a, r,g,b,a, r,g,b,a, ....];
    Zwracana tablica jest wielkości szerokość_canvas * wysokość_canvas * 4 a każda jej komórka zawiera wartość z przedziału 0-255
  • context.putImageData(naszImageData, x, y) - rysuje na canvasie w pozycji x,y piksele z imagedata.

Więcej informacji o ImageData znajdziesz pod adresem https://developer.mozilla.org/En/HTML/Canvas/Pixel_manipulation_with_canvas.

Przykładowe użycie powyższych metod pokazuje poniższy skrypt.
Rysujemy na płótnie super Fantomasa - to już znamy z poprzedniego przykładu.
Następnie pobieramy dane z naszego płótna do zmiennej myImgData. Pobieramy jej poszczególne składowe RGBa (do zmiennej data) i modyfikujemy każdą z nich.
Po tej operacji zapisujemy nasz zmodyfikowany obiekt myImgData na płótno.

Uwaga. W przeglądarce Chrome poniższe przykłady nie zadziałają gdy odpalimy je z własnego komputera. Jest to spowodowane względami bezpieczeństwa.
Pomoże przeniesienie ich na serwer ftp (tak jak na tej stronie) lub na serwer lokalny (localhost).


var canvas = document.getElementById('canvas_pxls');
var c = canvas.getContext('2d');
var fanthomas = new Image();

fanthomas.onload = function(){
    c.drawImage(fanthomas, 0, 0);

    //pobieramy dane z płótna
    var myImgData = c.getImageData(0, 0, canvas.width, canvas.height);

    //manipulujemy kolorami
    for (i=0; i < myImgData.data.length; i += 4) {
        myImgData.data[i] =   255 - myImgData.data[i]; //czerwony
        myImgData.data[i+1] = 255 - myImgData.data[i+1]; //zielony
        myImgData.data[i+2] = 255 - myImgData.data[i+2]; //niebieski
        // i+3 to alpha koloru
    }

    //rysujemy na płótnie nasz zmieniony obraz
    c.putImageData(myImgData, 276, 0);
};

fanthomas.src = 'fantomus.jpg';
Twoja przeglądarka nie obsługuje canvas

Powyższy przykład może i działa, ale nie robi tego zbyt efektownie. Czemu?
Głównie dlatego, że manipulacje kolorami przeprowadzamy BEZPOŚREDNIO na płótnie.
Wiemy już, że wszystkie operacje wykonywane na wyświetlanych na stronie elementach są bardzo wolne. Powyższe działanie nie jest wyjątkiem. Aby to poprawić, wystarczy przeprowadzić standardowy manewru super bohaterów, czyli podstawić pobraną "myImgData.data" podstawić pod zmienną. Pokazuje to poniższy bardzo podobny przykład. Tym razem zamiast odwracać kolory postaramy się je "wyrównać".


var canvas = document.getElementById('canvas_pxl2');
var c = canvas.getContext('2d');
var fanthomas = new Image();

fanthomas.onload = function(){
    c.drawImage(fanthomas, 0, 0);

    //pobieramy dane z płótna
    var myImgData2 = c.getImageData(0, 0, canvas.width, canvas.height);

    //pobieramy tablicę z kolorami powyższego obiektu
    var data = myImgData2.data;

    //manipulujemy kolorami
    for (i=0; i < data.length; i += 4) {
        var r = data[i];
        var g = data[i+1];
        var b = data[i+2];
        var a = data[i+3];
        var average = (r+g+b) / 3;
        data[i] = data[i+1] = data[i+2] = average;
    }

    myImgData2.data = data;

    //rysujemy na płótnie nasz zmieniony obraz
    c.putImageData(myImgData2, 276, 0);
};

fanthomas.src = 'fantomus.jpg';
Twoja przeglądarka nie obsługuje canvas

Małe, proste podstawienie pod zmienną, a szybkość działania znacznie się zwiększa. Więcej na ten temat możecie znaleźć tutaj: http://www.onaluf.org/en/entry/13

Ciekawy tutorial o działaniu na pikselach canvasu możesz znaleźć pod adresem: http://net.tutsplus.com/tutorials/javascript-ajax/canvas-from-scratch-pixel-manipulation/