Visit Mozilla.org

Przewodnik po canvas:Transformacje

z Mozilla Developer Center, polskiego centrum programistów Mozilli.

Spis treści

UWAGA: Tłumaczenie tej strony nie zostało zakończone.
Może być ona niekompletna lub wymagać korekty.
Chcesz pomóc? | Dokończ tłumaczenie | Sprawdź ortografię | Więcej takich stron...

[edytuj] Zapisywanie i odtwarzanie stanu

Zanim przyjrzymy sie metodom transformacji, zapoznajmy się z dwoma innymi metodami, które będą niezbędne, kiedy tylko zaczniemy tworzyć bardziej złożone rysunki.

save()
restore()

Metody save i restore są używane do zapisu i przywracania stanu canvas. Stan rysowania canvas jest zasadniczo obrazem wszystkich stylów i transformacji, które były zastosowane. Obie metody nie posiadają parametrów.

Stany canvas są przechowywane na stosach. Za każdym razem, gdy jest wywoływana metoda save, bieżący stan rysowania jest umieszczany na wierzchołku stosu. Stany rysowania składają się z

  • Transformacji, które były zastosowane (np. translacja, rotacja i skalowanie - zobacz poniżej).
  • Wartości własności strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, globalCompositeOperation.
  • Bieżącej ścieżki wycinającej, którą poznamy w następnym dziale.

Możemy wywołać metodę save wiele razy w zależności od zapotrzebowania. Za każdym razem, gdy jest wywołana metoda restore, ostatni zapisany stan jest zwracany ze stosu i wszystkie zapisane ustawienia są przywracane.

[edytuj] Przykład zapisu i odtworzenia stanu canvas

Ten przykład próbuje zilustrować, jak funkcjonuje stos ze stanami rysowania przy rysowaniu ustalonej sekwencji prostokątów.

Pierwszym krokiem jest narysowanie dużego prostokąta z domyślnymi ustawieniami. Następnie zapiszemy ten stan i zrobimy zmiany w kolorze wypełnienia. Wówczas narysujemy drugi, mniejszy prostokąt i zapiszemy stan. Znowu zmienimy niektóre ustawienia rysowania i narysujemy trzeci półprzeźroczysty biały prostokąt.

Dotąd jest to bardzo podobne do tego, co robiliśmy w poprzednich rozdziałach. Jednakże, skoro tylko wywołamy pierwszą instrukcję restore, znajdującą się na szczycie stosu, stan rysowania jest usuwany i ustawienia są przywracane. Jeżeli nie zapisalibyśmy stanu używając metody save, potrzebowalibyśmy ręcznie zmieniać kolor wypełnienia i przeźroczystość w przeciwieństwie do przywracania poprzedniego stanu. Byłoby to prostsze dla dwóch własności,jeżeli jednak mamy ich więcej, nasz kod stałby się bardzo szybko bardzo długi.

Kiedy druga instrukcja restore jest wywoływana , oryginalny stan (ustawienia przed pierwszym wywołaniem instrukcji save) jest przywracany i ostatni prostokąt jest znowu rysowany w czerni.

Zobacz ten przykład

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');

  ctx.fillRect(0,0,150,150);   //  Rysuje prostokąt według domyślnych ustawień 
  ctx.save();                  //  Zapisuje domyślny stan

  ctx.fillStyle = '#09F'       // Robi zmiany w ustawieniach 
  ctx.fillRect(15,15,120,120); // Rysuje prostokąt według nowych ustawień

  ctx.save();                  // Zapisuje aktualny stan
  ctx.fillStyle = '#FFF'       // Robi zmiany w ustawieniach 
  ctx.globalAlpha = 0.5;    
  ctx.fillRect(30,30,90,90);   // Rysuje prostokąt według nowych ustawień 

  ctx.restore();               // Przywraca poprzedni stan 
  ctx.fillRect(45,45,60,60);   // Rysuje prostokąt według przywróconych ustawień  

  ctx.restore();               // Przywraca oryginalny stan
  ctx.fillRect(60,60,30,30);   // Rysuje prostokąt według przywróconych ustawień
}

[edytuj] Translacja

Pierwszą z metod transformacji, której się przyjrzymy, jest translate. Ta metoda jest używana do przenoszenia punktu początkowego elementu canvas do różnych punktów siatki.

translate(x, y)

Ta metoda pobiera dwa argumenty. x odpowiada przesunięciu do lewej lub prawej elementu canvas , i y odpowiada jego przesunięciu w górę lub dół (zilustrowanemu przez grafikę po prawej).

Dobrym pomysłem jest zapisywanie stanu elementu canvas przed robieniem jakichkolwiek transformacji. W większości przypadków właściwie łatwiej jest wywołać metodę restore niż musieć odwrócić translację do powrotu do oryginalnego stanu. Także jeżeli dokonywalibyście translacji wewnątrz pętli nie zapisując stanu elementu canvas, moglibyście utracić w końcu część waszego rysunku, ponieważ zostałaby narysowana poza krawędzią elementu canvas.

[edytuj] Przykład translate

Ten przykład demonstruje niektóre korzyści przenoszenia punktu początkowego elementu canvas. Stwórzmy funkcję drawSpirograph, która rysuje wzory spirografu . Są one rysowane dookoła punktu początkowego. Jeżeli nie użylibyśmy funkcji translate, zobaczylibyśmy tylko ćwiartkę z wzoru na elemencie canvas. Metoda translate także daje nam swobodę do umieszczania punktu początkowego gdziekolwiek na elemencie canvas nie ustawiając ręcznie współrzędnych w funkcji spirografu. Ten sposób jest trochę łatwiejszy do zrozumienia i użycia.

W funkcji draw wywołujemy drawSpirograph dziewięć razy używając dwóch pętli for. W każdej pętli element canvas podlega przeniesieniu, jest rysowany spirograf, i element canvas jest z powrotem przywracany do początkowego stanu.

Zobacz ten przykład

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  ctx.fillRect(0,0,300,300);
  for (var i=0;i<3;i++) {
    for (var j=0;j<3;j++) {
      ctx.save();
      ctx.strokeStyle = "#9CFF00";
      ctx.translate(50+j*100,50+i*100);
      drawSpirograph(ctx,20*(j+2)/(j+1),-8*(i+3)/(i+1),10);
      ctx.restore();
    }
  }
}
function drawSpirograph(ctx,R,r,O){
  var x1 = R-O;
  var y1 = 0;
  var i  = 1;
  ctx.beginPath();
  ctx.moveTo(x1,y1);
  do {
    if (i>20000) break;
    var x2 = (R+r)*Math.cos(i*Math.PI/72) - (r+O)*Math.cos(((R+r)/r)*(i*Math.PI/72))
    var y2 = (R+r)*Math.sin(i*Math.PI/72) - (r+O)*Math.sin(((R+r)/r)*(i*Math.PI/72))
    ctx.lineTo(x2,y2);
    x1 = x2;
    y1 = y2;
    i++;
  } while (x2 != R-O && y2 != 0 );
  ctx.stroke();
}

[edytuj] Rotacja

Drugą metodą transformacji jest rotacja rotate. Używamy jej do obracania elementu canvas dookoła bieżącego punktu początkowego .

rotate(kąt)

Ta metoda pobiera tylko jeden parametr, którym jest kąt obrotu elementu canvas. Jest obracany zgodnie z ruchem wskazówek zegara i mierzony w radianach (zilustrowany na obrazie po prawej stronie) o.

Punktem centralnym obrotu jest zawsze punkt początkowy elementu canvas. Do zmiany punktu centralnego będziemy potrzebowali przemieścić element canvas poprzez użycie metody translate.

[edytuj] Przykład rotate

W przykładzie, który możemy zobaczyć po prawej, używamy metody rotate do rysowania figur w okrągły wzór. Możemy także obliczyć poszczególne współrzędne x i y (x = r*Math.cos(a); y = r*Math.sin(a)). W tym przypadku nie ma znaczenia, jaką wybierzemy metodę, ponieważ rysujemy okręgi. Wynikiem obliczenia współrzędnych jest tylko rotacja punktu środkowego z okręgami, a nie samych okręgów, dopóki używamy obydwa wyniki funkcji rotate, lecz oczywiście okręgi wyglądają tak samo, nie ma znaczenia, że są one obracane od swoich punktów środkowych.

Znowu mamy dwie pętle. Pierwsza określa liczbę pierścieni, a druga określa liczbę rysowanych kropek w każdym z pierścieni. Przed rysowaniem każdego pierścienia, zapiszmy stan elementu canvas, co pozwoli łatwo go przywrócić. Dla każdej kropki, którą rysujemy, obracamy miejsce współrzędnych przez kąt, który określa liczbę kropek w pierścieniu. Wewnętrzny pierścień posiada sześć kropek, co każdy krok obracanych o kąt 360/6 = 60 stopni. W każdym dodatkowym pierścieniu liczba kropek jest podwojona, a kąt jest zmniejszony o połowę.

Zobacz ten przykład

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  ctx.translate(75,75);

  for (i=1;i<6;i++){ // Pętla pociągająca okręgi (od wewnętrznej do zewnętrznej strony)
    ctx.save();
    ctx.fillStyle = 'rgb('+(51*i)+','+(255-51*i)+',255)';

    for (j=0;j<i*6;j++){ // rysuje poszczególne kropki
      ctx.rotate(Math.PI*2/(i*6));
      ctx.beginPath();
      ctx.arc(0,i*12.5,5,0,Math.PI*2,true);
      ctx.fill();
    }

    ctx.restore();
  }
}

[edytuj] Skalowanie

Ostatnią z metod transformacji jest skalowanie. Używamy jej do zwiększania lub zmniejszania jednostek w siatce naszego elementu canvas. Może być użyta do rysownia pomniejszonych lub powiększonych figur i bitmap.

scale(x, y)

Ta metoda pobiera dwa parametry. x jest współczynnikiem skalowania w kierunku poziomym i y jest współczynnikiem skalowania w kierunku pionowym. Oba parametry muszą być liczbami dodatnimi. Wartości mniejsze niż 1.0 zmniejszają rozmiar jednostki podstawowej, a wartości większe niż 1.0 zwiększają rozmiar jednostki podstawowej. Ustawienie współczynnika skalowania ściśle do 1.0 nie oddziałuje na rozmiar jednostki podstawowej.

Domyślna jednostka podstawowa w elemencie canvas wynosi dokładnie jeden piksel. Jeżeli zastosujemy przykładowo współczynnik skalowania 0.5, w rezultacie jednostka wyniesie 0.5 piksela i również figury będą rysowane w połowie rozmiaru. W podobnym sposób ustawiając współczynnik skalowania na 2.0 zwiększymy rozmiar jednostki i podstawowa jednostka będzie wynosiła teraz 2 piksele. W rezultacie figury będą rysowane dwukrotnie większe.

[edytuj] Przykład scale

W tym ostatnim przykładzie użyjemy funkcji spirografu z jednego z poprzednich przykładów do narysowania dziewięciu figur z różnym współczynnikiem skalowania. Górna figura z lewej strony została narysowana bez stosowania skalowania. Żółte figury do prawej obie posiadają jednolity współczynnik skalowania (taka sama wartość dla parametrów x i y ). Jeżeli spojrzymy na kod poniżej, zobaczymy użytą dwukrotnie metodę scale z różnymi wartościami parametrów dla drugiego i trzeciego spirografu. Dlatego że nie przywracamy stanu elementu canvas, trzecia figura jest rysowana z współczynnikiem skalowania 0.75 × 0.75 = 0.5625.

Drugi rząd niebieskich figur posiada niejednolite skalowanie stosowane w kierunku pionowym. Każda z figur ma współczynnik skalowania x ustawiony na 1.0, który oznacza brak skalowania. Współczynnik skalowania y jest ustawiony na 0.75. W rezultacie te trzy figury będą zgniecione w dól. Oryginalny okrągły kształt stał się teraz elipsą. Jeżeli przyjrzymy się bliżej szerokość linii, jest także zredukowana w kierunku pionowym.

Trzeci rząd zielonych figur jest podobny do tego powyżej, tylko teraz stosujemy skalowanie w kierunku poziomym.

Zobacz ten przykład

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  ctx.strokeStyle = "#fc0";
  ctx.lineWidth = 1.5;
  ctx.fillRect(0,0,300,300);

  // Jednolite skalowanie
  ctx.save()
  ctx.translate(50,50);
  drawSpirograph(ctx,22,6,5);  // Bez skalowania

  ctx.translate(100,0);
  ctx.scale(0.75,0.75);
  drawSpirograph(ctx,22,6,5);

  ctx.translate(133.333,0);
  ctx.scale(0.75,0.75);
  drawSpirograph(ctx,22,6,5);
  ctx.restore();

  // Niejednolite skalowanie (kierunek y)
  ctx.strokeStyle = "#0cf";
  ctx.save()
  ctx.translate(50,150);
  ctx.scale(1,0.75);
  drawSpirograph(ctx,22,6,5);

  ctx.translate(100,0);
  ctx.scale(1,0.75);
  drawSpirograph(ctx,22,6,5);

  ctx.translate(100,0);
  ctx.scale(1,0.75);
  drawSpirograph(ctx,22,6,5);
  ctx.restore();

  // Niejednolite skalowanie(kierunek x)
  ctx.strokeStyle = "#cf0";
  ctx.save()
  ctx.translate(50,250);
  ctx.scale(0.75,1);
  drawSpirograph(ctx,22,6,5);

  ctx.translate(133.333,0);
  ctx.scale(0.75,1);
  drawSpirograph(ctx,22,6,5);

  ctx.translate(177.777,0);
  ctx.scale(0.75,1);
  drawSpirograph(ctx,22,6,5);
  ctx.restore();
  
}

[edytuj] Transformacje

The final transformation methods allow modifications directly to the transformation matrix.

transform(m11, m12, m21, m22, dx, dy)

This method must multiply the current transformation matrix with the matrix described by:

m11 	m21 	dx
m12 	m22 	dy
0 	0 	1

If any of the arguments are Infinity the transformation matrix must be marked as infinite instead of the method throwing an exception.

setTransform(m11, m12, m21, m22, dx, dy)

This method must reset the current transform to the identity matrix, and then invoke the transform method with the same arguments. If any of the arguments are Infinity the transformation matrix must be marked as infinite instead of the method throwing an exception.

[edytuj] Przykłady transform / setTransform

function draw() {
  var canvas = document.getElementById("canvas");
  var ctx = canvas.getContext("2d");

  var i = 0;
  var sin = Math.sin(Math.PI/6);
  var cos = Math.cos(Math.PI/6);
  ctx.translate(200, 200);
  var c = 0;
  for (i; i <= 12; i++) {
    c = Math.floor(255 / 12 * i);
    ctx.fillStyle = "rgb(" + c + "," + c + "," + c + ")";
    ctx.fillRect(0, 0, 100, 10);
    ctx.transform(cos, sin, -sin, cos, 0, 0);
  }
  
  ctx.setTransform(-1, 0, 0, 1, 200, 200);
  ctx.fillStyle = "rgba(255, 128, 255, 0.5)";
  ctx.fillRect(0, 50, 100, 100);
}