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.
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, jesttranslate. 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.
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 rotacjarotate. 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ę.
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.
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);
}






