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.

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() ma postać:


ctx.drawImage(img, x, y)
img 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.


const canvasElem = document.querySelector('#canvasDraw');
const ctx = canvasElem.getContext('2d');
const image = new Image();
image.addEventListener('load', function(){
    ctx.drawImage(image, 0, 0, 262, 256);

    //tworzymy na grafice tekst "Fanthomas"
    ctx.font = "italic bold 30px Arial";
    ctx.textBaseline = "middle";
    ctx.textAlign = "center";
    ctx.fillStyle = "#4F3A73";
    ctx.strokeStyle = "#fff";
    ctx.lineWidth = 1;
    ctx.fillText('Fanthomas', 262/2, 256-20);
    ctx.strokeText('Fanthomas', 262/2, 256-20);
});

image.src = 'fantomus.jpg';

Skalowanie grafiki

Kolejna postać drawImage to:


ctx.drawImage(img, x, y, width, height);
img określa obrazek, który będziemy rysować na płótnie
x, y określają położenie rysowanego obrazka na płótnie
width, height rozmiar grafiki na canvasie

Grafika bez przekształceń:

domek


const canvasElem = document.querySelector('#canvasScale')
const ctx = canvasElem.getContext('2d');
const house = new Image();

house.addEventListener('load', function() {
    ctx.drawImage(house, 0, 0, 160, 160);
    ctx.drawImage(house, 180, 0, 120, 120);
    ctx.drawImage(house, 320, 0, 100, 100);
    ctx.drawImage(house, 430, 0, 140, 140);
    ctx.drawImage(house, 590, 0, 100, 160);
});

house.src = 'domek.jpg';

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

Wycinanie kawałka grafiki

Dla metody drawImage możemy też zastosować dodatkowe atrybuty okreslające położenie i rozmiar wycinanego kawałka oryginalnej grafiki.


ctx.drawImage(cx, cy, cWidth, cHeight, img, x, y, width, height);
cx, cy pozycja wycinanego kawałka w oryginalnej grafice
cWidth, cHeight rozmiar wycinanego kawałka w oryginalnej grafice
img grafika do wyświetlenia
x, y pozycja grafiki na canvasie
swidth, sheight rozmiar grafiki na canvasie

Przykładowo mamy grafikę z kostek - każda o rozmiarze 100x100:

Wycinanie grafiki dla canvas

I teraz chcielibyśmy na canvas wrzucić elementy [1,2,5,6] , [3,4,7,8], [9,10,13,14] i [11,12,15,16] ułożone poziomo obok siebie. Dodatkowo chcielibyśmy by wrzucone na canvas kawałki miały rozmiar nie 200x200 a 160x160:


const canvasElem = document.querySelector('#canvasCroop')
const ctx = canvasElem.getContext('2d');
const img = new Image();

img.addEventListener('load', function() {
    //[1,2,5,6]
    ctx.drawImage(img, 0, 0, 200, 200,        0, 0, 160, 160);
    //[3,4,7,8]
    ctx.drawImage(img, 200, 0, 200, 200,    165, 0, 160, 160);
    //[9,10,13,14]
    ctx.drawImage(img, 0, 200, 200, 200,    330, 0, 160, 160);
    //[11,12,15,16]
    ctx.drawImage(img, 200, 200, 200, 200,    495, 0, 160, 160);
});
img.src = './canvas-cut.jpg';

Manipulacja pikselami

Każde płótno (canvas) to uporządkowany zbiór pikseli. Lewy górny róg to początek, a dolny prawy do koniec.

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 rozmiary grafiki
data tablica 1-wymiarowa zawierająca informacje na temat kolejnych pikseli płótna. Każdy piksel definiują 4 kolejne indeksy:
i : czerwony, i+1 : zielony, i+2 : niebieski, i+3 : przezroczystość

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

context.createImageData(width, height)tworzy pusty obiekt typu ImageData o wymiarach podanych w parametrach. Wszystkie piksele zwróconego obiektu są przezroczyste
context.createImageData(innyImageData)zwraca obiekt typu ImageData o wymiarach takich samych jakie ma obiekt przekazany w parametrze. Tylko wymiary są kopiowane. Dane o pikselach nie są kopiowane.
context.getImageData(x, y, width, height)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
imageData.widthszerokość obiektu ImageData
imageData.heightwysokość obiektu ImageData
imageData.datazwraca 1 wymiarową tablicę, której poszczególnymi wartościami są składowe RGBa pikseli czyli
imageData.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(ImageData, x, y)rysuje na canvasie w pozycji x,y piksele z imagedata.

Przykład1 : podmiana kolorów

Mamy prostą grafikę kwiatka. Nie ma ona antyaliasingu (rozmycia pikseli) dla łatwiejszej manipulacji pikselami.

Naszym zadaniem jest pobrać dane tej grafiki, a następnie zamienić liście z koloru żółtego (rgb: 255,245,104) na czerwony (rgb: 255,0,0).

Kwiatek

Jak to zrobić? Musimy narysować na płótnie naszą grafikę - to już znamy z poprzednich przykładów.
Następnie pobierzemy dane z naszego płótna do zmiennej myImgData.
Po pobraniu takich danych wystarczy zrobić po nich pętlę (iterując co 4, bo każdy piksel opisany jest 4 składowymi: red, green, blue i alpha) i w razie czego modyfikować wartości kolorów danego piksela.
Po tej operacji zapisujemy nasz zmodyfikowany obiekt myImgData na płótno.


const canvasElem = document.querySelector('#canvasFleur');
const ctx = canvasElem.getContext('2d');
const img = new Image();

img.addEventListener('load', function() {
    //rysujemy kwiatek na płótnie
    ctx.drawImage(img, 0, 0);

    //pobieramy dane z płótna
    const myImgData = ctx.getImageData(0, 0, canvasElem.width, canvasElem.height);

    //zmieniamy kolor listków kwiatka
    for (let i=0; i<myImgData.data.length; i+=4) {
        if (myImgData.data[i] === 255) {
            myImgData.data[i] = 255;
        }
        if (myImgData.data[i+1] === 245) {
            myImgData.data[i+1] = 0;
        }
        if (myImgData.data[i+2] === 104) {
            myImgData.data[i+2] = 0;
        }
        // i+3 to alpha koloru
    }

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

img.src = 'kwiatek.png';

Przykład2 : odwrócenie kolorów

Kolejny przykład - bardzo podobny do poprzedniego polega na odwróceniu kolorów.


const canvasElem = document.querySelector('#canvas_pxls');
const ctx = canvasElem.getContext('2d');
const img = new Image();

img.addEventListener('load', function() {
    ctx.drawImage(img, 0, 0);

    //pobieramy dane z płótna
    const myImgData = ctx.getImageData(0, 0, canvasElem.width, canvasElem.height);

    //odwracam kolory
    for (let 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
    ctx.putImageData(myImgData, 276, 0);
});

img.src = 'fantomus.jpg';

Przykład3 : desaturacja kolorów

Kolejny przykład pokazuje jak zrównać kolory - czyli zrobić grafikę w odcieniach szarości:


const canvasElem = document.querySelector('#canvasPxl2');
const ctx = canvasElem.getContext('2d');
const fanthomas = new Image();

fanthomas.addEventListener('load', function() {
    ctx.drawImage(fanthomas, 0, 0);

    //pobieramy dane z płótna
    const myImgData = ctx.getImageData(0, 0, canvasElem.width, canvasElem.height);

    //ujednolicam kolory - wyliczając średnią
    for (let i=0; i < myImgData.data.length; i += 4) {
        const r = myImgData.data[i];
        const g = myImgData.data[i+1];
        const b = myImgData.data[i+2];
        const a = myImgData.data[i+3];
        const average = (r+g+b) / 3;
        myImgData.data[i] = myImgData.data[i+1] = myImgData.data[i+2] = average;
    }

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

fanthomas.src = 'fantomus.jpg';

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/