Canvas grafika

Funkcja drawImage()

Do rysowania danej grafiki na płótnie służy funkcja drawImage(), która występuje w 3 wariantach.

ctx.drawImage(image, dx, dy);
ctx.drawImage(image, dx, dy, dWidth, dHeight);
ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
image
grafika, którą będziemy rysować na płótnie. Może to być obiekt Image(), pobrany ze strony jakiś element img, czy pojedyncza klatka video.

dx, dy

pozycja na płótnie gdzie będziemy rysować

dWidth, dHeight

rozmiary rysowanej grafiki na płótnie

sx, sy

pozycja pobieranego wycinka na grafice źródłowej

sWidth, sHeight

rozmiary pobieranego wycinka z grafiki źródłowej

Kilka przykładów

const canvas = document.querySelector("canvas");
const ctx = canvas.getContext("2d");
const image = new Image();

image.addEventListener("load", () => {
    //proste rysowanie
    ctx.drawImage(image, 0, 0);

    //rysowanie z przeskalowaniem
    ctx.drawImage(image, 40, 40, canvas.width - 80, canvas.height - 80);

    //obwódka dla czytelności
    ctx.rect(40, 40, canvas.width - 80, canvas.height - 80);
    ctx.stroke();
});

image.src = "image.png";


Najedź kursorem na grafikę:


Przy omawianiu tablic wielowymiarowych pokusiliśmy się o wygenerowanie planszy. Za pomocą powyższych informacji moglibyśmy wreszcie wykonać je jak należy.

Pattern

Podobnie jak przy tle w CSS, także dla canvas możemy użyć powtarzania tła. Służy do tego funkcja createPattern(img, powtarzanie). Funkcja ta przyjmuje parametry:

img
wczytana wcześniej grafika

repeat

sposób powtarzania. Przyjmuje jedną z wartości: repeat, repeat-x, repeat-y i no-repeat

Zauważ, że nasz wzorek zaczyna się krzywo. Podobnie do transformacji aby wzór rozpoczynał się wraz z figurą, musimy posłużyć się funkcją translate:

Podobnie będzie przy pozostałych typach powtórzeń:

Manipulacja pikselami

Każda grafika rastrowa z canvasem włącznie 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

imageData.data

zwraca 1 wymiarową tablicę, której poszczególnymi wartościami są składowe rgba kolejnych 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

Element canvas udostępnia nam też 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

context.putImageData(ImageData, x, y)

rysuje na canvasie w pozycji x,y piksele z imageData.

Przykładowe manipulacje

Mając powyższe informacje, możemy pokusić się o przeprowadzenie prostych manipulacji na naszych grafikach. Jedną z nich jest chociażby odwracanie kolorów. Z powyższych informacji wiemy, że tablica imageData.data zawiera informacje o składowych r,g,b,a (w skali 0-255) kolejnych pikseli.

W podobieństwie do powyższej funkcji możemy równie łatwo wykonać inne manipulacje kolorami:

wyszarzenie kolorów rozjaśnienie kontrast

Generowanie na bazie płótna

Podejrzewam, że jeszcze nie do końca dostrzegasz olbrzymich mocy, jakie drzemią w powyższych funkcjach. Jeżeli na dane płótno wrzucisz jakąkolwiek grafikę, możesz następnie pobrać te dane i na ich podstawie robić dowolne efekty na stronie. Wrzucana grafika może pochodzić z wielu miejsc. Możesz więc ją rysować za pomocą odpowiednich funkcji, możesz ją pobrać za pomocą querySelector, możesz stworzyć grafikę i podać jej jakiś zewnętrzny adres, czy też pobrać ją bezpośrednio z video lub kamery internetowej.

Gdy taką grafikę wrzucisz na płótno o małych rozmiarach, a następnie pobierzesz z niego informacje o pikselkach, otrzymasz porcję danych idealnie nadającą się do generowania różnych efektów.

Sprawdźmy to. Powiedzmy, że mamy na stronie grafikę:

Na jej bazie chcemy wygenerować sobie nowy element składający się z kolorowych divów, gdzie każdy div będzie odpowiadał danemu pikselowi. Powyższa grafika ma rozmiar 530x530 co zmusiło by nas do wygenerowania 530 x 530 = 2809000 nowych divów. Dużo za dużo. Wrzućmy ją więc na jakiś mniejszy canvas:

Stwórzmy teraz element, w którym będziemy tworzyć divy oraz funkcję generującą odpowiedni html:

I tyle. Osobiście raczej nie polecam tworzyć takiej dużej liczby divów, a zamiast tego generować odpowiedni obraz na canvas, albo wygenerować wartość odpowiedniej właściwości (np. background, czy box-shadow).


Możemy też pokusić się o pójście o krok dalej, i wrzucać na canvas grafikę pobieraną bezpośrednio z input:file. W poniższym przykładzie dodatkowo przed wrzuceniem lekko przekształcę wynik korzystając z wcześniejszych funkcji:


Zamiast pojedynczych grafik możesz też generować całe animacje. Wystarczy płynnie pobierać kolejne klatki video, a następnie wrzucać je na płótno. W kolejnym rozdziale zobaczysz, że do takich rzeczy idealnie się nadaje funkcja requestFrameRate():

A możliwości stają się jeszcze większe, gdy dla takiego video będziesz pobierał obraz z kamerki (część poniższego kodu skopiowałem z tamtej strony).

Rozpocznij przechwytywanie

Na koniec przydało by się sam canvas i video ukryć dowolną techniką, a i odejście od generowania divów na rzecz czegoś innego na pewno mocno by poprawiło wydajność (generowanie tekstu, generowanie box-shadow, może rysowanie po canvas?). Swego czasu podobną techniką zrobiłem mini eksperyment związany z Bad Apple, gdzie właśnie korzystam z box-shadow.

Last updated