Zustände im Canvas

Zustände im Canvas ermöglichen uns das Speichern und spätere Wiederherstellen von bereits gesetzten Einstellungen. Mithilfe dieser Möglichkeit brauchen wir nicht jede Veränderung rückgängig machen, damit wir zu unserer ursprünglichen Einstellung zurückkehren können. Stattdessen brauchen wir nur eine einzige Funktion dafür aufrufen.

Jeder Kontext von unserem Canvas beinhaltet einen "Stack-Speicher". Dieser Begriff kommt aus dem Englischen und bedeutet Stapel-Speicher. Die Funktionalität können wir bereits vom Namen ablesen: Einstellungen die wir vornehmen, können wir mithile der Funktion save() auf den "Stapel" packen und später mit der Funktion restore() wiederherstellen.

Canvas Stack - CodingLab

In einem Zustand des Stapel-Speichers befinden sich folgende Informationen:

  • Die aktuelle Transformationsmatrix
  • Den aktuellen Clipping-Pfad
  • Die Werte der Eigenschaften:
    • strokeStyle
    • fillStyle
    • lineWidth
    • lineCap
    • lineJoin
    • lineJoin
    • miterLimit
    • font
    • shadowOffsetX
    • shadowOffsetY
    • shadowBlur
    • shadowColor
    • globalAlpha
    • globalCompositeOperation
  • Html
  • Javascript
  • Ergebnis
<html> <head></head> <body> <canvas id="canvas_id" width="600" height="250" style="border: 1px solid red;"></canvas> </body> </html>
var Canvas = document.getElementById( "canvas_id" ); var Context = Canvas.getContext( "2d" ); Context.strokeStyle = "#F00"; //Rot Context.font = "50px Lucida"; Context.strokeText( "CodingLab", 10, 40 ); Context.save(); Context.strokeStyle = "#0F0"; //Grün Context.font = "30px Arial"; Context.strokeText( "CodingLab", 10, 90 ); Context.save(); Context.strokeStyle = "#00F"; //Blau Context.font = "15px Arial"; Context.strokeText( "CodingLab", 10, 140 ); Context.restore(); Context.strokeText( "CodingLab", 10, 190 ); Context.restore(); Context.strokeText( "CodingLab", 10, 240 );

Im obigen Beispiel haben wir die Eigenschaften strokeStyle und font zweimal mit der Methode save() gespeichert. Jedes Mal wurde ein neuer Zustand im Stapel angelegt. Nach dem 3. Schriftzug haben wir das erste Mal die Methode restore() angewendet und somit die letzte Einstellung mit der grünen Schriftfarbe wiederhergestellt. Beim zweiten Aufruf von restore() wurde der Ausgangszustand wiederhergestellt. Mit jedem restore() wird quasi eine Platte vom Stapel weggenommen.

Canvas verschieben

Die Methode translate( x, y ) verschiebt unser Canvas und nimmt folgende Parameter an:

ParameterBeschreibung
xGibt die Pixelanzahl an, um die das Canvas in horizontale Richtung verschoben wird.
yGibt die Pixelanzahl an, um die das Canvas in vertikale Richtung verschoben wird.

Wichtig dabei ist, dass wir unser gesamtes Canvas verschieben. Das Element selbst bleibt im HTML-DOM an der gleichen Stelle stehen. Die Anzeigefläche jedoch verschiebt sich komplett:

  • Html
  • Javascript
  • Ergebnis
<html> <head></head> <body> <canvas id="canvas_id" width="600" height="200" style="border: 1px solid red;"></canvas> </body> </html>
var Canvas = document.getElementById( "canvas_id" ); var Context = Canvas.getContext( "2d" ); Context.fillRect( 10, 10, 50, 50 ); Context.translate( 100, 0 ); Context.fillRect( 0, 10, 50, 50 );

Canvas skalieren

Wenn wir unser Canvas skalieren wollen, können wir die Methode scale( x, y ) mit folgenden Parametern benutzen:

ParameterBeschreibung
xGibt den Faktor für die horizontale Skalierung an.
yGibt den Faktor für die vertikale Skalierung an.

Wenden wir diese an, so werden die Einheiten des Canvas mit dem angegebenen Faktor multipliziert:

  • Html
  • Javascript
  • Ergebnis
<html> <head></head> <body> <canvas id="canvas_id" width="600" height="200" style="border: 1px solid red;"></canvas> </body> </html>
var Canvas = document.getElementById( "canvas_id" ); var Context = Canvas.getContext( "2d" ); Context.fillRect( 10, 10, 50, 50 ); Context.scale( 2, 2 ); Context.fillRect( 50, 5, 50, 50 ); Context.scale( 0.5, 0.5 ); Context.fillRect( 240, 10, 50, 50 );

Im obigen Beispiel können wir klar erkennen, dass der Faktor 2 unsere Einheiten verdoppelt. Ein Faktor von 4 würde unser Quadrat 200px/200px groß wirken lassen. Folglich verkleinert ein Faktor von 0.5 unsere Canvas Einheiten. Diese sind dann nur noch die Hälfte so groß wie vorher. Allerdings haben wir den Faktor ja vorher verdoppelt, weswegen wir mit dem zweiten scale() Aufruf den Ursprung wiederhergestellt haben. Das Resultat ist ein identisches Quadrat wie am Anfang.

Nun können wir kurzzeitig unseren Zustand speichern, um anschließend ein bestimmtes Objekt, das wir zeichnen, zu vergrößern. Wir brauchen dann kein zweites scale() mit dem Konträr aufrufen, sondern können unsere vorherigen Einstellungen mithilfe von restore() wiederherstellen:

  • Html
  • Javascript
  • Ergebnis
<html> <head></head> <body> <canvas id="canvas_id" width="600" height="200" style="border: 1px solid red;"></canvas> </body> </html>
var Canvas = document.getElementById( "canvas_id" ); var Context = Canvas.getContext( "2d" ); Context.save(); Context.scale( 20, 10 ); Context.fillRect( 0, 0, 10, 10 ); Context.restore(); Context.fillRect( 240, 10, 10, 10 );

Canvas spiegeln

Wenn wir die Faktoren für die Methode scale() ins Negative setzen, erreichen wir, dass unsere Objekte auf dem Canvas sich spiegeln:

  • Html
  • Javascript
  • Ergebnis
<html> <head></head> <body> <canvas id="canvas_id" width="600" height="200" style="border: 1px solid red;"></canvas> </body> </html>
var Canvas = document.getElementById( "canvas_id" ); var Context = Canvas.getContext( "2d" ); Context.font = "40px Lucida"; Context.save(); Context.scale( -1, 1 ); Context.fillText( "CodingLab", -200, 50 ); Context.restore(); Context.fillText( "CodingLab", 20, 150 );

Zunächst speichern wir den ursprünglichen Zustand. Anschließend setzen wir die Skalierung auf x = -1 und y = 1. Für die Y-Koordinate können wir normale Werte eintragen, jedoch müssen wir bei der X-Koordinate umdenken und immer die entgegen gesetzten Werte nehmen. Aus diesem Grund haben wir für den Parameter x der fillText Methode den Wert -200 genommen. Damit wird der Text bei 200 angefangen zu zeichnen und von rechts nach links - also gespiegelt - weggeschrieben.

Canvas rotieren

Mit der Methode rotate( a ) können wir unser Canvas von seinem Ursprung aus rotieren. Der einzige Parameter den wir hier angeben müssen ist der Winkel, mit dem rotiert wird:

  • Html
  • Javascript
  • Ergebnis
<html> <head></head> <body> <canvas id="canvas_id" width="600" height="200" style="border: 1px solid red;"></canvas> </body> </html>
var Canvas = document.getElementById( "canvas_id" ); var Context = Canvas.getContext( "2d" ); Context.rotate( (Math.PI/180)*10 ); Context.strokeRect( 0, 0, Canvas.width, Canvas.height ); Context.fillRect( 50, 50, 100, 100 );

Im obigen Beispiel haben wir das gesamte Canvas am Punkt 0/0 um 10 Grad rotiert. Da unser Canvas als Winkelgröße Radianten annimmt, müssen wir zunächst unsere Gradzahl umrechnen. (PI/180) * Grad lautet unsere Formel. Als nächstes haben wir um unser geändertes Canvas einen Rand gezogen, der verdeutlichen soll, wie viel rotiert wurde. Als letztes haben wir ein Quadrat an der Stelle 50/50 in der Größe 100px/100px gezeichnet. Dieses ist ebenfalls um 10 Grad rotiert.

Um jetzt unser Quadrat um sich selbst zu drehen, behelfen wir uns eines Tricks mit der Funktion translate(). Im wesentlichen geht man dabei in folgenden Schritten vor:

  1. Das Canvas zum Mittelpunkt des Rechtecks verschieben.
    • translate( xRect, yRect );
  2. Das Canvas rotieren.
    • rotate( Angle );
  3. Das Canvas um den negativen Mittelpunkt des Rechtecks verschieben.
    • translate( -xCenterRect, -yCenterRect );
  4. Das Rechteck im Ursprung zeichnen.
    • fillRect( 0, 0, wRect, hRect );
  • Html
  • Javascript
  • Ergebnis
<html> <head></head> <body> <canvas id="canvas_id" width="600" height="200" style="border: 1px solid red;"></canvas> </body> </html>
var Canvas = document.getElementById( "canvas_id" ); var Context = Canvas.getContext( "2d" ); Context.fillStyle = "#F00"; //Rot Context.fillRect( 50, 100, 100, 50 ); Context.fillStyle = "#00F"; //Blau Context.translate( 100, 125 ); Context.rotate( (Math.PI/180)*25 ); Context.translate( -50, -25 ); Context.fillRect( 0, 0, 100, 50 );

Im obigen Code haben wir zunächst ein Rechteck an der Stelle 50/100 gezeichnet, damit das Ergebnis klarer ist. Nun haben wir unser gesamtes Canvas zum Mittelpunkt dieses gezeichneten Rechtecks verschoben. Alles was nun an der Stelle 0/0 gezeichnet wird, fängt in der Mitte des Rechtecks an.

Als nächstes rotieren wir unser Canvas. Es wird immer noch in der Mitte des Rechtecks gezeichnet, nur mit der gewünschten Rotation. Allerdings, wollen wir nicht die obere linke Ecke unseres Rechtecks an dieser Stelle zeichnen, sondern die Mitte, weswegen wir noch einmal die Hälfte davon abziehen. Dafür verschieben wir die Hälfte in negative Richtung.

Die Transformations-Matrix

Alle Transformationen funktionieren im Canvas mit einer sogenannten Transformations-Matrix:

Canvas Transformations Matrix - CodingLab

Wie jede Matrix können wir uns auch diese als Tabelle vorstellen. Hier haben wir also in der ersten Zeile der ersten Spalte den Wert m11 und folglich in der zweiten Zeile der dritten Spalte den Wert dy. Hier Matrizen zu erklären, würde definitiv den Rahmen sprengen. Allerdings möchte ich die einzelnen Werte und ihre Bedeutung kurz erklären:

WertBeschreibung
m11Wert für die horizontale Skalierung.
m12Wert für die horizontale Verzerrung.
m21Wert für die vertikale Verzerrung.
m22Wert für die vertikale Skalierung.
dxWert für die horizontale Verschiebung.
dyWert für die vertikale Verschiebung.

Die dritte Zeile ist hier nicht erklärt, da sie Standardwerte vordefiniert hat. Was können wir nun mit der Transformations-Matrix anfangen? Ganz einfach, wir können einzelne Werte dieser Matrix setzen und somit unsere eigene Transformation durchführen.

Canvas transformieren

Um unser Canvas nach unseren Wünschen zu transformieren, müssen wir die Transformations-Matrix verändern. Das können wir mit der Methode transform( m11, m12, m21, m22, dx, dy ), welche die einzelnen Parameter mit den Werten der bestehenden Transformations-Matrix multipliziert:

  • Html
  • Javascript
  • Ergebnis
<html> <head></head> <body> <canvas id="canvas_id" width="600" height="200" style="border: 1px solid red;"></canvas> </body> </html>
var Canvas = document.getElementById( "canvas_id" ); var Context = Canvas.getContext( "2d" ); Context.fillRect( 50, 50, 50, 50 ); var m11 = 1, m12 = 0, dx = 200; // 1 0 200 var m21 = 0, m22 = 1, dy = 50; // 0 1 50 // 0 0 1 //Translations-Matrix (Verschiebung um X=200 | Y=50) Context.transform( m11, m12, m21, m22, dx, dy ); Context.fillRect( 50, 50, 50, 50 );

Im obigen Beispiel haben wir eine einfache Translations-Matrix gesetzt, die unser Quadrat verschiebt. Der Vorteil, den wir mit transform() nutzen können, ist die Kombination aus Skalierung, Rotation, Verschiebung und sonstigen Transformationen.

Wenden wir die obige Matrix ein weiteres Mal an, so würden wir ein weiteres Mal unser Canvas um 200px/50px verschieben. Wenn wir allerdings eine Multiplikation mit der Ursprungs-Matrix haben wollen, müssen wir die Methode setTransform( m11, m12, m21, m22, dx, dy ) benutzen. Diese nimmt die gleichen Parameter an, multipliziert die Matrix vorher allerdings mit einer Einheitsmatrix, sodass wir den Ursprung wiederherstellen:

  • Html
  • Javascript
  • Ergebnis
<html> <head></head> <body> <canvas id="canvas_id" width="600" height="200" style="border: 1px solid red;"></canvas> </body> </html>
var Canvas = document.getElementById( "canvas_id" ); var Context = Canvas.getContext( "2d" ); var m11 = 1, m12 = 0, dx; // 1 0 dx var m21 = 0, m22 = 1, dy; // 0 1 dy //Rechteck bei 10/10 Context.transform( m11, m12, m21, m22, 10, 10 ); Context.fillRect( 0, 0, 50, 50 ); //Rechteck bei 60/60 Context.setTransform( m11, m12, m21, m22, 60, 60 ); Context.fillRect( 0, 0, 50, 50 ); //Rechteck bei 110/110 Context.setTransform( m11, m12, m21, m22, 110, 110 ); Context.fillRect( 0, 0, 50, 50 );