1177550

Video-Manipulation mit Canvas in HTML 5

30.08.2011 | 15:05 Uhr |

HTML 5 soll in der nächsten Zukunft fertig sein - das erzählt man sich jedenfalls. Jene, die dies verbreiten, verzichten meist darauf, "fertig" näher zu definieren. Fakt ist nämlich: Viele Aspekte von HTML5 kann man heute schon verwenden.

Web-Standard HTML5
Vergrößern Web-Standard HTML5

Zu einer der offensichtlichen Neuerungen in HTML 5 gehört das Attribut <canvas> , das die Manipulation von Filmdaten direkt im Browser erlaubt. Im Grunde genommen, sind solche Funktionen schon längst mit der gängigen Videobearbeitungs-Software abgedeckt. Der einzige Unterschied ist wahrscheinlich, dass der Nutzer die Autorenrolle übernimmt und weit mehr mit dem Video anstellen kann, als nur abspielen und gegebenfalls herunterladen.

Canvas-Grundlagen

Für die Effektmaschine im Browser braucht man an HTML-Elementen nichts weiter als ein Video-Element, zwei Canvas-Elemente und einen Script-Block:

<!doctype html>

<video id="film" width="320" height="180" controls>

<source src="video.mp4" type="video/mp4">

<source src="video.ogv" type="video/ogg">

</video>

<canvas id="zwischenablage" width="320" height="180" style="display:none"></canvas>

<canvas id="ziel" width="320" height="180"></canvas>

<script></script>

Zwei getrennte Canvas-Elemente braucht man in diesem Fall, weil die Video-Bearbeitung in zwei Schritten erfolgt (die einzelnen Pixel auf einem Video-Element kann man nur lesen - Ändern geht nur bei Canvas-Elementen). Dafür kopiert man die Video-Frames vom Canvas-Element in Canvas 1 und von dort in Canvas 2. Da Canvas 1 nur als Zwischenablage dient, kann man sie auch mit display:none unsichtbar machen. Jetzt fehlt nur noch DOM-Zugriff auf die drei Elemente …

// Elemente in der Seite

var film = document.getElementById('film'),

ziel = document.getElementById('ziel').getContext('2d');

zwischenablage = document.getElementById('zwischenablage').getContext('2d');

… und schon kann es losgehen.

Die richtige Framerate abpassen

Das Ziel ist also nun, jedes Frame des Videos abzupassen, zu kopieren und zu modifizieren. Das ist schwieriger als man vielleicht zunächst meint, denn herkömmliche JavaScript-Timing-Mechanismen ( setTimeout() und setInterval() ) sind nicht besonders exakt; mit ihnen kann man nicht garantieren, dass man jedes Frame erwischt oder dass man nicht manche Frames zweimal kopiert. Die korrekte Lösung für dieses Problem ist eine Funktion namens requestAnimationFrame() , die auf das nächste vom Browser gerenderte Frame wartet und dann einen Callback ausführt. Dieses Werkzeug sorgt, rekursiv angewendet, für einen präzisen Animations-Loop.

Der Haken an der Sache ist, dass requestAnimationFrame() eine ziemlich neue Erfindung ist und in den diversen Browsern nur mit Vendor-Prefix zu finden ist. Damit man an dieser Stelle diese Funktion für jeden Browser einzeln programmiert, gibt es bereits eine Modifikation im Code, die die Verwendung von requestAnimationFrame() in allen Browsern erlaubt:

// Modifikation für requestAnimationFrame in allen Browsern

var animate = (function(){

return window.requestAnimationFrame ||

window.webkitRequestAnimationFrame ||

window.mozRequestAnimationFrame ||

window.oRequestAnimationFrame ||

window.msRequestAnimationFrame ||

function(callback){

setTimeout(callback, 1000 / 60);

};

})();

Hier wird dafür gesorgt, dass der Nutzer unter animate die bestmögliche Implementierung von requestAnimationFrame() vorfindet, die der Browser zu bieten hat; zur Not auch eine Variante mit Vendor-Prefix oder, wenn nichts davon funktioniert, auch ein herkömmlicher setTimeout() .

Damit man auch eine Animationsschleife erhält, muss man animate wieder und wieder aufrufen, wobei jeder Aufruf der Funktion ein einzelnes Frame abhandelt. Auch diese Funktionalität wird wieder in eine Hilfsfunktion versteckt:

// Die Kopier-Schleife

function loop(){

if(!film.paused){

animate(loop);

}

}

Hier wird loop() immer wieder automatisch aufgerufen - jeweils durch den requestAnimationFrame() gesteuert und nur so lange, wie das Video läuft. Jetzt fehlt nur noch die Auslösung für die Kopier-Schleife, wofür das Video-Element einen passenden Event bereitstellt:

// Die Kopier-Schleife starten

film.addEventListener('play', function(){

loop();

}, false);

Zu diesem Zeitpunkt kann man die Animationsschleife starten, laufen lassen und stoppen. Damit mit dem Video wirklich etwas passiert, muss man noch ein paar weitere Funktionen programmieren.

Pixel in Canvas kopieren

Auf ein Canvas-Element kann man mit der drawImage() ganz einfach fertige Grafiken zeichnen - dazu gibt man einfach neben den Ziel-Koordinaten ein HTML-Element an, das als Datenquelle dienen soll. Als Quelle können neben <img>- Elementen auch Canvas- oder Video-Elemente fungieren, da man die Videoframes in die Zwischenablage kopieren kann. Um in die Pixeldaten des Frames zu kopieren, muss man die Funktion getImageData() in der Zwischenablage aktivieren. Diese gibt ein so genanntes ImageData-Objekt zurück, das die Farbwerte jedes einzelnen Pixels enthält. Aktiviert man die gleiche Funktion für die Ziel-Canvas, ist die Bilder- bzw. Videokopie-Funktion komplett.

// Diese Funktion kopiert Pixel von "film" in "zwischenablage", dann in "ziel"

function copy(){

zwischenablage.drawImage(film, 0, 0);

var bilddaten = zwischenablage.getImageData(0, 0, 320, 180);

ziel.putImageData(bilddaten, 0, 0);

}

Dazu kommt noch der Aufruf der copy()- Funktion in dem Animationsloop

// Die Kopier-Schleife

function loop(){

if(!film.paused){

copy(); // Frames kopieren

animate(loop);

}

}

Durch das Doppelschritt beim Kopieren hat man mit dem ImageData-Objekt den Zugriff auf die Farbwerte jedes einzelnen Pixels in jedem Videoframe, so dass es nicht schwer fällt, die Farben des Bildes zu verändern.

Videos mit Canvas modifizieren

Ein ImageData-Objekt enthält neben den Angaben width und height (die Maße des Ausschnitts) ein Array, das der Reihe nach alle Farbwerte aller Pixel enthält. Der erste Eintrag im Array ist der Rot-Wert des ersten Pixels, der zweite Eintrag ist der Grün-Wert des ersten Pixels, an dritter Stelle folgt der Blau-Wert des ersten Pixels, dann kommt der Alpha-Wert des ersten Pixels und dann der Rot-Wert des zweiten Pixels … und so weiter. Diese Werte effektvoll zu manipulieren ist sehr einfach:

// Wendet den Effekt an

function effekt(bilddaten){

var pixel = bilddaten.data;

var i = 0;

var r, g, b, new_r, new_g, new_b;

while(i < pixel.length){

// R, G und B holen...

r = pixel[i],

g = pixel[i + 1],

b = pixel[i + 2];

// Manipulieren...

new_r = Math.min(255, r * 0.393 + g * 0.769 + b * 0.189),

new_g = Math.min(255, r * 0.349 + g * 0.686 + b * 0.168),

new_b = Math.min(255, r * 0.272 + g * 0.534 + b * 0.131);

// ... und speichern!

pixel[i] = new_r;

pixel[i + 1] = new_g;

pixel[i + 2] = new_b;

// Auf zum nächsten Pixel - den Alphawert einfach überspringen

i += 4;

}

return bilddaten;

}

Die Funktion arbeitet sich einmal durch das Bilddaten-Array, verändert die RGB-Werte der Pixel, überspringt den Alpha-Wert und gibt am Ende ein komplett überarbeitetes ImageData-Objekt zurück. Dieses muss man dann nur noch abbilden, d.h. die Funktion effekt() in copy() wird einmal auf die Bilddaten anwendet

// Diese Funktion kopiert Pixel von "film" in "zwischenablage", dann in "ziel"

function copy(){

zwischenablage.drawImage(film, 0, 0);

var bilddaten = zwischenablage.getImageData(0, 0, 320, 180);

bilddaten = effekt(bilddaten); // Effekt anwenden

ziel.putImageData(bilddaten, 0, 0);

}

Und so sieht ein Video aus, das in Canvas manipuliert wurde .

Fazit

Ein paar Pixel zu modifizieren ist in Canvas mit HTML 5 nicht besonders schwer. Wollte man wirklich Videodaten erst im Browser generieren, wäre das aber im Prinzip auch kein Hexenwerk: mit der createImageData()-Methode des 2D-Kontext (Spezifikationen) kann man leere Bilddatensätze erstellen und diese dann mit Farbwerten für die diversen Pixel befüllen. Die Herausforderung liegt tatsächlich eher in der Codierung und Decodierung von Bilddaten, denn man will schließlich in Sachen Wiedergabe weder von dem löchrigen Codec-Support der Browser abhängig sein, noch möchte man darauf verzichten, bei Effektgeneratoren wie im Demo am Ende fertige Filme abzuspeichern. In diesem Bereich liegen die wahren Herausforderungen. Die Werkzeuge sind jedoch alle da. Nun sind die Entwickler dran.

0 Kommentare zu diesem Artikel
1177550