Vor Kurzem habe ich eine Mail von einem Nutzer meines QR Code Generators erhalten. Er befasst sich gerade tiefer mit der Funktionsweise von QR Codes und hat die Frage gestellt, ob die ganzen Modultypen, aus denen sich QR Codes zusammensetzen, zum besseren Verständnis unterschiedlich hervorgehoben werden können. Aus meiner Sicht ist es eine klassische Aufgabenstellung, die mit Hilfe des Vektorgrafikformats (SVG), garniert mit einer Priese von DOM-Manipulationen gelöst werden kann. Die Frage ist nur, ob es mir auch ohne clientseitige JavaScript-Logik gelingt. Finden wir’s heraus.
Bisheriger SVG-Aufbau
Bisher hat mein QR Code Generator nur statische SVG-Dateien generiert, die in ihrer Einfachheit kaum zu überbieten waren. Sie bestanden neben dem Rootknoten<svg> lediglich aus einer Ansammlung von Kinderknoten vom Typ <rect>. Jeder dieser Knoten repräsentierte jeweils ein QR Code Modul und definierte sich über seine Position auf der X und Y Achse sowie über seine Farbe, festgelegt im Attribut fill.
<?xml version = '1.0' encoding = 'UTF-8'?>
<svg xmlns="http://www.w3.org/2000/svg">
<rect x="0" y="0" width="8" height="8" fill="white" />
<rect x="8" y="0" width="8" height="8" fill="white" />
....
<rect x="504" y="512" width="8" height="8" fill="white" />
<rect x="512" y="512" width="8" height="8" fill="white" />
</svg>
Die C#-Generierungsmethode hierfür war ebenfalls sehr einfach gehalten und hat in zwei verschachtelten For-Schleifen alle in Matrixform organisierten QR-Modul-Objekte iteriert und für jedes Objekt ein <rect> Knoten erstellt. Die Positionen auf der X und Y Achse wurden durch die Multiplikation der Zeile- bzw. Spaltennummer mit der Breite des <rect> Knotens ermittelt.
Anpassungen für die Animationen
Auf dieser Basis setzte ich auf und werde eine weitere C#-Methode implementieren, welche die Generierung von animierten SVG-Dateien übernimmt. In den darauf folgenden Kapiteln gehen wir die zusätzlichen SVG-Knoten Schritt für Schritt durch.
Die erste Anpassung ist die Generierung einer Art Menüführung für den Nutzer. Als Menüeinträge fungieren dabei ganz normale Überschriften, die mit Hilfe von <text> Knoten unterhalb des Wurzelknotens <svg> realisiert sind. Jede dieser Überschriften repräsentiert einen bestimmten QR Code Modultyp und hat die Aufgabe, alle Module desselben Typs farblich hervorheben zu lassen.
Dabei werden nicht alle möglichen QR-Modultypen als Auswahl aufgeführt, sondern nur die, die im jeweiligen QR Code auch tatsächlich enthalten sind. Dafür analysiert die C#-Generierungsmethode den darzustellenden QR Code vorab und speichert alle seine vorkommenden Modultypen und die Anzahl der Module von diesem Typ in einem Dictionary. Anhand dieser Einträge werden dann die Überschriften generiert. So haben beispielweise kleinere QR Codes dann keine Versionmodule und keine Alignmentmodule.
Meine ursprüngliche Idee war, dass ein Klick auf die jeweilige Modultypüberschrift zweierlei Dinge auslöst. Zum einen sollen alle QR Module des angeklickten Typs farblich markiert werden. Zum anderen soll auch die angeklickte Modultypüberschrift hervorgehoben werden, sodass ein Klick als solcher erkennbar ist. Ein erneuter Klick auf die selbe Modultypüberschrift soll die ganzen Markierungen wieder rückgängig machen. Mit JavaScript wäre das kein Problem – damit würde sich der aktuelle Zustand der jeweiligen Überschrift (markiert oder nicht markiert) bequem abfragen und entsprechende Umkehrung (von markiert zu unmarkiert, bzw. von unmarkiert zu markiert) realisieren lassen. Da SVG keine Programmiersprache ist, ist damit keine Fallunterscheidung möglich. So löst ein Klick auf die Modultypüberschrift daher immer nur die Markierung der jeweiligen Module und die Markierung der Überschrift aus. Um diese Markierung wieder rauszunehmen, musste ich mich des im Web recht unüblichen Doppelklicks-Events bedienen. Wenn ich mich diesbezüglich irren sollte und jemand weiß, wie sich mit SVG eine Art Toggle-Funktionalität ohne JavaScript mit dem einheitlichen Klickevent umsetzen lässt, wäre ich für einen entsprechenden sehr Tipp dankbar. Man lernt ja bekanntlich nie aus.
Schauen wir nun ein Beispiel eines Modultypüberschrift-Knotens an, um nachvollziehen zu können, wie dieses markiert / demarkiert wird. Jeder dieser Knoten bekommt eine ID, über die dann Animationen gestartet werden. Die ID ist einfach der Name des Modultyps (Enum SequenceBitType):
internal enum SequenceBitType : byte
{
EncodingModeModul,
CharacterCountIndicatorModul,
MessageModul,
TerminatorModul,
PaddingBitModul,
PaddingByteModul,
RemainderModul,
MessageErrorCorrectionModul,
AlignmentpatternModul,
DarkModul,
FinderpatternModul,
SeparatorModul,
TimingpatternModul,
QuietzoneModul,
FormatReservationModul,
FormatErrorCorrectionLevelModul,
FormatMaskPatternNumberModul,
FormatErrorCorrectionModul,
VersionModul,
VersionErrorCorrectionModul
}
Um die Zustandsveränderungen (von nicht markiert zu markiert und wieder zurück) zu beschreiben, sind je zwei <animate> Knoten pro Überschrift notwendig. Im Attribut attributeName wird der Name des Attributes hinterlegt, welches animiert werden soll. In diesem Beispiel ist es das Attribut fill, also die Textfarbe.
<text x="306" y="195" id="FinderpatternModul">FinderpatternModul
<tspan visibility="hidden"> (147)
<animate
attributeName="visibility"
begin="FinderpatternModul.click"
to="visible"
dur="1ms"
fill="freeze"/>
<animate
attributeName="visibility"
begin="FinderpatternModul.dblclick"
to="hidden"
dur="1ms"
fill="freeze"/>
</tspan>
<animate
attributeName="fill"
begin="FinderpatternModul.click"
to="blue"
dur="1ms"
fill="freeze"/>
<animate
attributeName="fill"
begin="FinderpatternModul.dblclick"
to="black"
dur="1ms"
fill="freeze"/>
</text>
Im Attribut begin wird festgelegt, welches Ereignis die Animation auslösen soll. Dabei sind unterschiedliche Ausprägungen möglich. Zum Einen kann es ein zeitlicher Offset sein, beispielweise 3s. Das würde bedeuten, dass die Animation 3 Sekunden nach dem Laden der SVG-Datei beginnt. Zum Anderen kann auch ein bestimmtes Mausereignis als Auslöser einer Animation hinterlegt werden. So wie im oben gezeigten Beispiel leitet ein Klick auf die Überschrift mit der ID FinderpatternModul die Markierung und ein Doppelklick auf die selbe Überschrift die Demarkierung ein. Eine Kombination eines zeitlichen Offsets und eines Mausereignisses wäre ebenfalls möglich. So würde die Ausprägung FinderpatternModul.mouseover + 1s die Animation nach einer Sekunde starten lassen, nach dem die Maus über die entsprechende Überschrift geschwebt ist.
Mit den Attributen from und to wird beschrieben, mit welchem Zustand die Animation starten und bei welchem enden soll. In diesem Beispiel habe ich auf die Festlegung des Startzustands verzichtet und lediglich den Zielzustand, also die Zielfarbe der Überschrift mitgegeben. So springt bei der Markierung die Farbe auf braun und bei der Demarkierung auf die Farbe schwarz.
Das Attribut dur legt fest, wie lange der Übergang vom Start- bis zum Zielzustand dauern soll. In diesem Beispiel soll die Markierung / Demarkierung sofort erfolgen, daher habe ich als Dauer eine Millisekunde hinterlegt.
Zu guter Letzt haben wir noch das Attribut fill, mit dem festgelegt wird, was am Ende der Animation passieren soll. In meinem Beispiel habe ich mich für freeze entschieden. Diese Ausprägung führt dazu, dass die Endfarbe am Ende der Animation beibehalten wird. Eine weitere erwähnenswerte Ausprägung lautet remove, die zur Rückkehr zur Ursprungsfarbe am Ende der Animation führen würde.
Aufmerksamen Lesern mag vielleicht der <tspan> Knoten aufgefallen sein, der mit ebenfalls mit zwei <animate> Knoten ausgestattet ist. Dieser Knoten beinhaltet die Anzahl der Module des jeweiligen Typs und wird beim Klick auf die Typüberschrift sichtbar gemacht. Ein Doppelklick lässt diese Anzahl dann wieder verschwinden.
Die Generierung dieser <animate> Knoten erfolgt durch die C#-Methode nach einer recht einfachen Logik, da nahezu alle Attribute statisch sind – lediglich die ID der auslösenden Überschrift und die Anzahl der Module ist je Knoten individuell mitgegeben.
farbliche Hervorhebung von Modulen
Damit beim Klick auf eine Modultypüberschrift im Auswahlmenü die richtigen Module hervorhoben werden, erhält jeder <rect> Knoten je zwei individuell ausgeprägte <animate> Knoten für die Animation seiner eigenen Markierung / Demarkierung.
<rect x="56" y="40" width="8" height="8" fill="white">
<animate
attributeName="fill"
begin="FinderpatternModul.click"
dur="1ms"
to="grey"
fill="freeze" />
<animate
attributeName="fill"
begin="FinderpatternModul.dblclick"
dur="1ms"
to="white"
fill="freeze" />
</rect>
<rect x="80" y="40" width="8" height="8" fill="black">
<animate
attributeName="fill"
begin="FinderpatternModul.click"
dur="1ms"
to="brown"
fill="freeze" />
<animate
attributeName="fill"
begin="FinderpatternModul.dblclick"
dur="1ms"
to="black"
fill="freeze" />
</rect>
Hier in diesem Listing sehen wir, dass die Überschrift mit der ID FinderpatternModul nicht nur die Hervorhebung der eigenen Überschrift auslöst. Sie sorgt auch dafür, dass alle Module, bei denen diese als Auslöser mit dem click-Event hinterlegt ist, ebenfalls einen entsprechenden Farbwechsel erfahren.
Die C#-Generierungsmethode für diese <animate> Knoten ist etwas komplexer aufgebaut. Diese durchläuft jedes Modul der QR Code Matrix durch und liest zum einen den Bittyp (siehe Listing enum SequenceBitType), der dieses Modul repräsentiert und zum anderen auch die aktuelle (maskierte) Farbe des Moduls. Anhand dieser werden die <animate> Knoten entsprechend ausgeprägt. So wird der Bittyp, der zugleich die ID des auslösenden Überschrift darstellt, mit dem jeweiligen Eventnamen als begin eingetragen (click für die Markierung und dblclick für die Demarkierung). Anhand der Farbe des aktuellen Moduls erhalten die beiden <animate> Knoten individuelle Zielfarben (im Attribut to). So werden weiße Module durch einen Farbübergang zu grau und schwarze Module durch einen Wechsel zu braun markiert. Die Demarkierung erfolgt dann in der umgekehrten Reihenfolge.
Zick-Zack-Platzierung der Datenmodule
Mal Hand aufs Herz – bei den bislang vorgestellten SVG Animationen kann man ja noch nicht wirklich von Animationen, sondern eher von Hervorhebungen sprechen. Um auch die <animateMotion> Knoten zum ersten Mal auszuprobieren, habe ich versucht, die Zick-Zack-Bewegung zu animieren, die beim Platzieren von Datenmodulen im QR Code zum Einsatz kommt. Als Datenmodule werden alle Module verstanden, welche Nutzdaten, Nutzmetadaten oder Fehlerkorrekturen der Nutzdaten repräsentieren:
- EncodingModeModul
- CharacterCountIndicatorModul
- MessageModul
- TerminatorModul
- PaddingBitModul
- PaddingByteModul
- RemainderModul
- MessageErrorCorrectionModul
Für diese Animation werden bei den relevanten <rect> Knoten zusätzlich zu den bisherigen <animate> Knoten noch zwei weitere <animateMotion> generiert und hinzugefügt.
<rect x="256" y="256" width="8" height="8" fill="white">
<animateMotion
path="M 500,-151"
begin="StartBitwiseAnimation.click"
dur="1ms"
fill="freeze" />
<animateMotion
path="M 500,-151 L 0,0"
begin="StartBitwiseAnimation.click+150ms"
dur="2s"
fill="freeze" />
</rect>
<rect x="248" y="256" width="8" height="8" fill="white">
<animateMotion
path="M 508,-151"
begin="StartBitwiseAnimation.click"
dur="1ms"
fill="freeze" />
<animateMotion
path="M 508,-151 L 0,0"
begin="StartBitwiseAnimation.click+300ms"
dur="2s"
fill="freeze" />
</rect>
Wird die Animation durch einen Klick auf den Textknoten mit der ID StartBitwiseAnimation gestartet, werden alle Datenmodule sofort an dieselbe bestimmte Stelle in der SVG-Datei verschoben und dort direkt aufeinander gelegt. Dies erfolgt mit Hilfe des Path-Attributs, der bei jedem Knoten die absolute Entfernung ab dem eigenen Standort im QR Modul zum „Sammelort“ auf der X und Y Achse beschreibt. Die zweite Animation dauert bei jedem <rect> Knoten je 2 Sekunden und beschriebt die Bewegung des Moduls vom „Sammelort“ zurück zu seinem ursprünglichen Standort im QR Code. In der SVG-Datei sieht es dann wie ein kurzer Flug aus. Um die ZickZack-Bewegung nachzubilden, habe ich mich eines kleinen Tricks bedient. So habe ich in der C#-Klasse (DataPlacer.cs um genau zu sein), die fürs Platzieren der einzelnen Datamodule zuständig ist, bei jedem Datenmodul die Reihenfolge abgespeichert, in der es der QR Code Matrix hinzugefügt wurde. Diese Reihenfolge, oder besser gesagt der Index wird dann für die ZickZack-Animation in Millisekunden-Offset übersetzt. Dafür habe ich eine Verzögerungskonstante mit dem Wert von 150 Millisekunden festgelegt. Sprich – das erste Modul startet seine Animation 150 Millisekunden, nach dem die Startüberschrift angeklickt wurde. Das zweite Modul ist nach 300 Millisekunden nach dem Klick dran und so weiter. So startet für jedes Modul die eigene Animation mit einer individuellen Verzögerung, sodass diese Zickzack-Bewegung nachgebildet wird.
Auch bei dieser Animation bin ich SVG-technisch erstmal an die Grenzen gestoßen. Meine Idee war es, dass der Start der Zickzack-Animation die Startüberschrift solange deaktiviert, solange die Animation noch läuft, um mehrfache Starts zu unterbinden. Auch hier wäre die JavaScript -Lösung deutlich einfacher. Mit der reinen SVG-Lösung habe ich aktuell keine Möglichkeit gefunden. Auch hier bin ich für einen Hinweis dankbar, ob es überhaupt möglich ist. Weiterer Punkt, der gerade noch nicht zufriedenstellend läuft, ist, dass die animierten Module teilweise unterhalb fester Module einfliegen und so von diesen verdeckt werden.
Fazit
Die hier vorgestellten Animationen waren sehr einfach und kratzen gerade mal an der Oberfläche der Animationsmöglichkeiten. Mit der SVG-Technologie ist die Abbildung wesentlich komplexerer Bewegungsabläufe möglich. Hierzu sind, wie man es bereits gesehen hat, keine großen Kenntnisse erforderlich. Die <animate> und <animateMotion> Knoten abstrahieren die Komplexität hinsichtlich der Bewegungen weg, sodass man für Bewegungsanimationen sich nicht mit Matrizenmultiplikationen zu befassen braucht. Wobei ich sagen muss, dass die Definition von komplexen Path-Ausdrücken, welche die Bewegungen beschreiben, aus meiner Sicht alles andere als intuitiv und einfach ist. Ohne gute Doku ist man da echt aufgeschmissen. Hier habe ich die Webseite verlinkt, die mir bei der Recherche gut weitergeholfen hat.
Das tolle an der SVG-Grafik ist, dass sich diese, wie XML auch, mit absolut jeder Programmiersprache und Technologie erzeugen lässt. Die meisten haben dafür entsprechende vordefinierte Klassen (wie zum Beispiel C# oder VBA und viele andere). Aber selbst, wenn keine Klassen zur Verfügung stehen, lassen sich diese Grafiken (mit etwas mehr Schreibaufwand) ohne Probleme erzeugen.
Hier direkt unterhalb des Absatzes habe ich eine erzeugte SVG-Datei eingebunden, bei der man die im Artikel beschrieben Animationen und Hervorhebungen mit Klick / Doppelklick auf die jeweiligen Typen ausprobieren lassen. Das Einscannen des QR Codes könnt ihr euch spanten – viel mehr als „ein besonders geistreicher Text…“ steht da nicht drin.