Mit diesem Experiment will ich ein Stück weit die Grenzen des Machbaren ausreizen. Gezeichnet werden die Pixel direkt in die Canvas. Keine extra 3D Engine, bzw. Bibliothek ist hier sonst im Einsatz. Und auch kein WebGL. Somit verzichte ich hierbei ganz bewusst auf Hardwarebeschleunigung.
Auf einem Desktop-Rechner werden die knapp über 130.000 Partikel trotzdem sauber mit 60 FPS berechnet.
Als Model habe ich mich in diesem Fall für das „T800 Terminator Genisys“ Model von Machina Corp. entschieden. Der Grund dafür ist ganz einfach. Möglichst viele Details.
Ich habe das Model in Blender bearbeitet und dann als DAE exportiert. Aus dieser 16MB großen DAE habe ich dann die Werte für die Koordinaten vom Mesh kopiert und dann minifiziert in eine TXT-Datei eingefügt. Auf diese Weise konnte ich das Datenvolumen auf 2MB reduzieren.
Mit jQuery wird dann die so erzeugte TXT geladen und die Daten mit den Koordinaten von einem String in ein Array umgewandelt. Die jetzt folgenden Schritte basieren dann im Prinzip nur noch auf meinen älteren Experimenten.
Eigentlich wollte ich nur mal schauen wieviel Partikel ich im dreidimensionalen Raum auf einmal darstellen/berechnen kann bei akzeptabler Performance. Und hier gibt’s das Ergebnis:
Für den Fall das ihr das Experiment in einem extra Browser Tab anschauen wollt könnt ihr einfach hier klicken.
Posts mit dem Label Pixel werden angezeigt. Alle Posts anzeigen
Posts mit dem Label Pixel werden angezeigt. Alle Posts anzeigen
5/19/2017
2/18/2016
Sound Visualizer V2
Auf Basis von meinem ersten Canvas Sound Visualizer habe ich eine neue Version erstellt.
Wie gehabt könnt ihr die Szenerie mit der Maus drehen und so die 20 unterschiedlichen 3D-Modelle von allen Seiten betrachten.
Mit einem Klick wechselt ihr nicht nur die Modelle, sondern ändert auch das Farbschema. Alles Weitere geschieht von allein. Ihr könnt also Einfluss nehmen, aber müsst es nicht. Der Sound Visualizer kann euch auch ohne Interkation unterhalten.
Bis auf das Gesicht werden alle Modelle dynamisch erstellt und dank diverser Zufallsparameter nehmen bestimmte Modelle bei jedem Start neue Positionen usw. ein, weshalb sich jedes Mal ein neues Bild ergibt. Für mich war es in diesem Fall besonders schwierig einen schönen Übergang beim Wechsel zwischen den Modellen zu realisieren, aber im Endeffekt ist das ganz gut gelungen wie ich finde.
Die verschiedenen Modelle bestehen jeweils aus 32768 Partikeln. Bei bestimmten Modellen, wie z.B. beim Gesicht, werden zusätzliche Partikel mit angezeigt, was die Gesamtzahl auf bis zu 65536 Partikel erhöht die gleichzeitig dargestellt und berechnet werden.
Jedes einzelne Partikel reagiert in einer bestimmten und vorher definierten Art und Weise auf den Audio Input. Dadurch und in Verbindung mit den unterschiedlichen Farbschemen entstehen in der Regel ziemlich einzigartige Bilder.
Bitte gebt dem Sound Visualizer etwas Zeit zum Laden. Bei Interesse/Gefallen empfehle ich den kompletten Song durchlaufen zu lassen, damit ihr alle Modelle und Farben im Einsatz sehen könnt.
Falls euch der Song „Sleepless“ von „Hypp fractal“ aus dem Album „Glow“ nicht gefällt, dann könnt ihr über das Menü eure eigenen MP3´s laden und schauen wie sich der Sound Visualizer dazu verhält.
Generell könnt ihr über das Menü diverse Einstellungen vornehmen. Der Fullscreen Mode ist dabei etwas mit Vorsicht zu genießen, da es sehr viel Perfomance kostet die Canvas auf Bildschirmgröße darzustellen. Mit einem flotten Rechner sollte das aber gut funktionieren.
Mobile und Internet Explorer User gehen leider leer aus. Das liegt nicht an der Engine selbst, also den Partikeln usw., sondern daran, dass die Browser auf mobilen Endgeräten kaum, oder nur sehr schlecht, Audiowiedergabe unterstützen. Und in diesem Fall wird die Soundquelle nicht nur wiedergegeben, sondern auch analysiert. Ich hoffe das wird mit zukünftigen Browser Versionen besser und einheitlicher werden, aber bis dahin gibt es bei mobilen Endgeräten leider nichts auf die Ohren und somit können die Partikel auch nicht reagieren. Probiert es aber gerne mal aus und gebt mir Feedback falls es läuft. Aber da es sich hier nur um ein kleines Experiment handelt wollte ich nicht zu viel Zeit investieren das auch auf mobilen Geräten ans Laufen zu bekommen.
Ihr könnt den Sound Visualizer direkt hier auf der Seite starten. Am besten aber folgt ihr dem Link hier und öffnet das Ganze in einem neuen Browser Fenster.
Wie gehabt könnt ihr die Szenerie mit der Maus drehen und so die 20 unterschiedlichen 3D-Modelle von allen Seiten betrachten.
Mit einem Klick wechselt ihr nicht nur die Modelle, sondern ändert auch das Farbschema. Alles Weitere geschieht von allein. Ihr könnt also Einfluss nehmen, aber müsst es nicht. Der Sound Visualizer kann euch auch ohne Interkation unterhalten.
Bis auf das Gesicht werden alle Modelle dynamisch erstellt und dank diverser Zufallsparameter nehmen bestimmte Modelle bei jedem Start neue Positionen usw. ein, weshalb sich jedes Mal ein neues Bild ergibt. Für mich war es in diesem Fall besonders schwierig einen schönen Übergang beim Wechsel zwischen den Modellen zu realisieren, aber im Endeffekt ist das ganz gut gelungen wie ich finde.
Die verschiedenen Modelle bestehen jeweils aus 32768 Partikeln. Bei bestimmten Modellen, wie z.B. beim Gesicht, werden zusätzliche Partikel mit angezeigt, was die Gesamtzahl auf bis zu 65536 Partikel erhöht die gleichzeitig dargestellt und berechnet werden.
Jedes einzelne Partikel reagiert in einer bestimmten und vorher definierten Art und Weise auf den Audio Input. Dadurch und in Verbindung mit den unterschiedlichen Farbschemen entstehen in der Regel ziemlich einzigartige Bilder.
Bitte gebt dem Sound Visualizer etwas Zeit zum Laden. Bei Interesse/Gefallen empfehle ich den kompletten Song durchlaufen zu lassen, damit ihr alle Modelle und Farben im Einsatz sehen könnt.
Falls euch der Song „Sleepless“ von „Hypp fractal“ aus dem Album „Glow“ nicht gefällt, dann könnt ihr über das Menü eure eigenen MP3´s laden und schauen wie sich der Sound Visualizer dazu verhält.
Generell könnt ihr über das Menü diverse Einstellungen vornehmen. Der Fullscreen Mode ist dabei etwas mit Vorsicht zu genießen, da es sehr viel Perfomance kostet die Canvas auf Bildschirmgröße darzustellen. Mit einem flotten Rechner sollte das aber gut funktionieren.
Mobile und Internet Explorer User gehen leider leer aus. Das liegt nicht an der Engine selbst, also den Partikeln usw., sondern daran, dass die Browser auf mobilen Endgeräten kaum, oder nur sehr schlecht, Audiowiedergabe unterstützen. Und in diesem Fall wird die Soundquelle nicht nur wiedergegeben, sondern auch analysiert. Ich hoffe das wird mit zukünftigen Browser Versionen besser und einheitlicher werden, aber bis dahin gibt es bei mobilen Endgeräten leider nichts auf die Ohren und somit können die Partikel auch nicht reagieren. Probiert es aber gerne mal aus und gebt mir Feedback falls es läuft. Aber da es sich hier nur um ein kleines Experiment handelt wollte ich nicht zu viel Zeit investieren das auch auf mobilen Geräten ans Laufen zu bekommen.
Ihr könnt den Sound Visualizer direkt hier auf der Seite starten. Am besten aber folgt ihr dem Link hier und öffnet das Ganze in einem neuen Browser Fenster.
Labels:
Audio,
Canvas,
HTML5,
ImageData,
JavaScript,
JS,
Particles,
Pixel,
Sound,
Visualizer
1/26/2016
HTML5 Tutorial: Canvas Pixel Manipulation
In diesem Tutorial wird beschrieben, wie ihr mit Hilfe vom ImageData Objekt Zugriff auf die Pixel des HTML5 Canvas Elements bekommt.
Zuerst schauen wir uns die Grundlagen an bevor wir tiefer in die Materie eintauchen.
Voraussetzung für dieses Tutorial ist, dass ihr bereits mit dem Canvas Element vertraut seid und wisst wie ihr damit umzugehen habt. Und natürlich solltet ihr zumindest grundliegende Kenntnisse in JavaScript haben.
Das Thema „Pixel Manipulation“ ist die Grundlage so ziemlich von allen Experimenten die ich in letzter Zeit hier auf meinem Blog veröffentlich habe. Hier eine Auswahl an Experimenten (Die meisten mit Partikeln in 3D, dazu wird es ein weiteres Tutorial geben):
Los geht’s.
Was ist das ImageData Objekt,
wie erstellen wir eins und wie kommen wir an die Pixelinformationen?
Das ImageData Objekt hat 3 Eigenschaften (Read only):
Die beiden Eigenschaften width und height beinhalten die Werte für die Breite und Höhe.
Die data Eigenschaft beinhaltet die Pixelinformationen an die wir gelangen wollen und mit deren Hilfe wir Zugriff auf die Pixel bekommen. ImageData.data ist im Prinzip nichts anderes als ein Array. Genauer gesagt ein Uint8ClampedArray das die Pixelinformationen in der Reihenfolge RGBA beinhaltet mit Werten von 0 – 255. Später mehr dazu.
Zunächst einmal erstellen wir ein ImageData Objekt:
Wir haben nun mit Hilfe der Methode createImageData() ein leeres ImageData Objekt erstellt mit einer Breite und Höhe von 100 Pixeln. Alle Pixel in diesem neuen Objekt sind per Voreinstellung auf schwarz mit einer Transparenz von 0 gesetzt. Oder anders ausgedrückt, jedes Pixel hat folgende Werte:
Neben der Methode createImageData() gibt es noch eine weitere Möglichkeit ein ImageData Objekt zu erstellen. Ihr könnt mit Hilfe der Methode getImageData() auch die Pixelinformationen aus einer Canvas auslesen.
Im folgenden Beispiel erstellen wir wieder eine Canvas. Aber im Gegensatz zum vorherigen Beispiel färben wir dieses Mal die Canvas per fillRect rot ein. Dementsprechend sind die RGBA Pixelinformationen die wir erhalten anders als im ersten Beispiel.
Beispiel.
Nun haben wir ein ImageData Objekt erstellt das die Pixelinformationen der Canvas enthält.
Da wir unsere Canvas rot eingefärbt haben besitzen die Pixel nun folgende Werte:
Wir haben also zwei Methoden zur Auswahl mit denen wir ein ImageData Objekt erstellen können.
Die Methode createImageData() eignet sich vor allem dann, wenn man ein völlig neues und leeres „Bild“ als Grundlage haben möchte. Mit der Methode getImageData() hingegen kann man z.B. die Pixel eines geladenen Bildes auslesen und verändern.
Schauen wir uns nun die Werte genauer an die wir aus dem data Array auslesen können. Zunächst einmal die Grundlagen zu RGBA:
Im folgenden Beispiel lesen wir die Daten für das erste Pixel aus unserem per getImageData() erstellten imageData Objekt aus:
Wir wissen nun die Farbe des ersten Pixels (links oben) und können dem Beispiel entnehmen, dass die ersten vier Werte aus dem data Array, nämlich 0, 1, 2 und 3 ein Pixel repräsentieren. Folgerichtig besteht jedes Pixel aus vier Werten (r, g, b, a) und das hilft uns dabei wenn wir über das data Array iterieren wollen um an alle Pixelinformationen zu gelangen.
Um über das data Array zu iterieren nehmen wir eine übliche for-Schleife:
Auf diese Weise erhalten wir die RGBA Farbinformationen eines jeden im data Array gespeicherten Pixels. Der Trick hierbei besteht darin den Schlüssel i nicht um den Wert 1 zu erhöhen (i++), sondern um 4 (i += 4).
Denn, wie wir bereits wissen, besteht jedes Pixel aus 4 Werten (r, g, b, a) und somit kommen wir in jedem Schritt der for-Schleife an die Farbinformationen eines Pixels.
Hier gibt es noch ein sehr gutes Tutorial genau zu dem Thema.
Um das iterieren über das Array zu optimieren empfehle ich unbedingt 2 Änderungen an dem obigen Beispiel vorzunehmen. Zum einen speichern wir imgData.data in einer extra Variablen und die for-Schleife schreiben wir wie folgt auch etwas anders.
Auf diese Weise läuft das Auslesen der Pixelinformationen performanter ab. In den folgenden Beispielen setze ich voraus das data als extra Variable gespeichert ist.
Hier noch ein schöner Link mit weiteren Tipps zur Optimierung.
Auslesen ist das eine,
aber nun gehen wir einen Schritt weiter und verändern die Farbinformationen.
Im folgenden Beispiel geben wir allen Pixeln aus unserem data Array die RGBA Werte für die Farbe Grün.
Grün sieht so aus:
Und nun ändern wir die Werte, die zuvor noch rot waren entsprechend in grün um:
Wir haben nun allen Pixeln die im data Array enthalten sind neue Werte zugewiesen.
Doch wieso wirkt sich diese Änderung nicht aus? Unsere Canvas ist immer noch rot und nicht grün.
Damit sich unsere „Pixel Manipulation“ auswirkt müssen wir noch einen Schritt durchführen:
Mit der Methode putImageData() kopieren wir die von uns abgeänderten Pixelinformationen des ImageData Objekts zurück in die Canvas an die Position x = 0 und y = 0.
Und nun ist unsere Canvas nicht mehr rot, sondern grün.
Hier nun zusammengefasst das komplette Beispiel:
Beispiel in JSFiddle.
Wir verfügen nun über die Grundlagen und wissen wie wir Pixelinformationen auslesen und verändern können.
Natürlich macht es wenig Sinn eine komplette Fläche, also eine komplette Canvas auf diese Weise von einer Farbe in eine andere umzufärben. Dafür gibt es auch andere Methoden für die wir nicht einmal Zugriff auf die Pixel benötigen. Um aber beispielhaft zu erklären wie das ganze grundliegend funktioniert ist es dann eben doch sinnvoll.
Folgend ein etwas anderes Beispiel, welches man ohne den Zugriff auf die Pixel so nicht umsetzen könnte. Wir geben jetzt jedem einzelnen Pixel aus dem data Array per random einen zufälligen Farbwert:
Beispiel in JSFiddle.
Und hier noch drei weitere JSFiddle Beispiele. Dieses Mal laden wir ein externes Bild und zeichnen dieses, nachdem es geladen worden ist, per drawImage in die Canvas und verändern dessen Pixelinformationen.
Beispiel 1: Farben invertieren
Beispiel 2: Ein Graustufenbild erzeugen
Beispiel 3: Per Schwellenwertverfahren Farbwerte zufällig segmentieren
Hier noch ein externes Tutorial zu diesem Thema, das etwas tiefer in die Materie Filter eintaucht und sehr schöne Beispiele beinhaltet.
Anhand dieser Beispiele sollte klar werden wie mächtig der Zugriff auf die Pixel sein kann.
Achtung: Wenn wir mit der Methode drawImage ein Bild in die Canvas zeichnen und Zugriff auf die Pixel haben wollen, dann muss sich dieses Bild auf dem gleichen Server befinden auf dem auch das JavaScript ausgeführt wird! Ist das nicht der Fall wird die Konsole eures Browsers eine Fehlermeldung ausgeben und der Zugriff auf die Pixel wird verweigert:
Man kann das umgehen. Aber der Weg ist steinig und nicht in jedem Fall durchzuführen.
Das Stichwort hierbei lautet CORS. Vereinfacht ausgedrückt: Der Besitzer des Bildes muss euch den Zugriff auf das Bild erlauben. Wie man das macht wird hier beschrieben.
Wenn ihr dann ein solches Bild von einem anderen Server laden wollt, dann müsst ihr unbedingt die folgende Zeile in eurem Script ergänzen:
Wichtig hierbei. Diese extra Zeile muss vor der Zeile eingefügt werden in der ihr den src-Pfad des Bildes angebt. Hier ein Beispiel wie ihr ein solches Bild laden könnt:
Und hier noch einmal zum Vergleich ein Beispiel wie ihr ein Bild vom gleichen Server laden könnt:
Das aber nur kurz zur Erklärung. Nicht das ihr euch wundert, wenn ihr ein Bild von einem anderen/fremden Server ladet und dann die hier gelernten Inhalte nicht funktionieren.
Kommen wir nun aber zurück zum eigentlichen Inhalt dieses Tutorials.
Bisher haben wir einfach immer nur alle Pixel eines Bildes ausgelesen und/oder verändert. Was aber wenn wir gezielt einzelne Pixel ansprechen/auslesen wollen?
Dazu brauchen wir zwei Funktionen/Methoden, die es uns erlauben anhand von XY-Koordinaten einzelne Pixel aus dem data Array anzusprechen.
Fangen wir damit an einzelne Pixel aus dem data Array per XY-Koordinaten zu verändern.
Das Problem. Wir bekommen per data Array nur über den Index Zugriff auf die einzelnen Werte.
Daher müssen wir einen kleinen Trick anwenden um unsere X und Y Werte in den Index umzurechnen. Eigentlich ganz einfach. Und so wird’s gemacht:
Verpacken wir das jetzt in einer Funktion, die als Paramater X, Y und ein Objekt (c) mit den Farbwerten RGBA entgegen nimmt, kommt das dabei raus:
Wir haben nun eine Funktion, die es uns erlaubt die Farbwerte eines per XY-Koordinaten ausgewählten Pixels zu verändern.
Dabei ist zu beachten, dass die X und Y Werte ganzzahlige Werte ohne Komma und Dezimalstellen sind. Schließlich gibt es keine Pixel auf Koordinaten mit Komma. Es gilt also, wenn vorhanden, Fließkommazahlen (Float) vorab in Integer umzuwandeln. Vereinfacht ausgedrückt: Bevor wir die Funktion setPixel aufrufen, müssen wir dafür sorgen, dass die XY-Koordinaten keine Kommas mehr enthalten. Es gibt mehrere Wege das zu tun. Ich zeige hier 2 Möglichkeiten auf, wobei ich immer die zweite Variante verwende.
Variante 1 mit Math.floor():
Variante 2 mit Bitwise OR operator:
In beiden Beispielen färben wir jeweils ein Pixel schwarz ein. Daher auch die Variable c, bzw. das Objekt c mit den Werten für RGBA:
Wie wir sehen beinhalten die Variablen für die XY-Koordinaten Fließkommazahlen. Einfach nur um das mit der Umwandlung zu verdeutlichen. Würden wir an dieser Stelle keine Umwandlung vornehmen, dann würden die hier gezeigten Beispiele versagen und die Funktion setPixel wäre nicht mehr in der Lage aus den XY-Koordinaten den Index für das Array zu berechnen.
Ich bevorzuge die Variante 2 mit dem Bitwise OR operator, weil das, wenn man sehr viele Pixel Zugriffe auf einmal hat, einfach ein Stück weit performanter läuft als mit Math.floor().
Und noch eine Kleinigkeit gilt es bei der Funktion setPixel zu beachten. Führt ihr Berechnungen durch, bei denen es vorkommen kann, dass die XY-Koordinaten außerhalb des Bereichs der Canvas liegen, dann müsst/solltet ihr durch eine zusätzliche If-Bedingung sicherstellen, dass diese nicht gezeichnet werden. Das führt sonst zu komischen, manchmal auch interessanten, aber bestimmt nicht gewollten Effekten. Hier ein Beispiel dafür, mit der Annahme, dass unsere Canvas eine Breite und Höhe von 100 Pixeln hat und das unser Pixel außerhalb davon liegt:
Hier noch einmal als Beispiel in JSFiddle mit Koordinaten innerhalb der Canvas.
Jetzt, wo wir wissen wie man die Farbwerte eines Pixels verändert, soll es nun darum gehen die Farbinformationen bestimmter Pixel auszulesen. Die folgende Funktion nimmt die Parameter X und Y entgegen und gibt ein Objekt zurück mit den Werten RGBA:
In diesem Beispiel in JSFiddle lesen wir per Zufall den Farbwert eines Pixels aus.
Mit den beiden Methoden setPixel und getPixel haben wir nun ganz komfortabel Zugriff auf die einzelnen Pixel der Canvas. Von hier an stehen uns im Prinzip alle Türen offen.
Folgend ein paar JSFiddle Beispiele mit denen wir per XY-Koordinaten die Pixel der Canvas verändern und somit Bilder aus Code generieren.
In einem weiteren Tutorial, das auf diesem aufbauen wird, werde ich dann erklären wie ihr die dritte Dimension und Partikel (wie hier bei diversen Beispielen schon gezeigt) auf eure Canvas bringt.
Hier noch zum Abschluss eine kleine aber feine Auswahl an deutschsprachigen Tutorials rund um dieses Thema:
Ich hoffe das Tutorial hat euch gefallen. Falls noch irgendetwas unklar sein sollte hinterlasst einfach einen Kommentar oder schreibt mir. Ich würde mich freuen wenn ihr wieder vorbei schaut zu Teil 2.
Zuerst schauen wir uns die Grundlagen an bevor wir tiefer in die Materie eintauchen.
Voraussetzung für dieses Tutorial ist, dass ihr bereits mit dem Canvas Element vertraut seid und wisst wie ihr damit umzugehen habt. Und natürlich solltet ihr zumindest grundliegende Kenntnisse in JavaScript haben.
Das Thema „Pixel Manipulation“ ist die Grundlage so ziemlich von allen Experimenten die ich in letzter Zeit hier auf meinem Blog veröffentlich habe. Hier eine Auswahl an Experimenten (Die meisten mit Partikeln in 3D, dazu wird es ein weiteres Tutorial geben):
- Thresholding Crossfader
- Neural Network V2
- Gravitational Field
- Hier kommt die Sonne
- 3D Fireworks
- Pumping Torus Knot
- Sinus Landscape
Los geht’s.
Was ist das ImageData Objekt,
wie erstellen wir eins und wie kommen wir an die Pixelinformationen?
Das ImageData Objekt hat 3 Eigenschaften (Read only):
ImageData.width ImageData.height ImageData.data
Die beiden Eigenschaften width und height beinhalten die Werte für die Breite und Höhe.
Die data Eigenschaft beinhaltet die Pixelinformationen an die wir gelangen wollen und mit deren Hilfe wir Zugriff auf die Pixel bekommen. ImageData.data ist im Prinzip nichts anderes als ein Array. Genauer gesagt ein Uint8ClampedArray das die Pixelinformationen in der Reihenfolge RGBA beinhaltet mit Werten von 0 – 255. Später mehr dazu.
Zunächst einmal erstellen wir ein ImageData Objekt:
var canvas = document.createElement( 'canvas' );
canvas.width = 100;
canvas.height = 100;
document.body.appendChild( canvas );
var context = canvas.getContext( '2d' );
var imageData = context.createImageData( 100, 100 );
Wir haben nun mit Hilfe der Methode createImageData() ein leeres ImageData Objekt erstellt mit einer Breite und Höhe von 100 Pixeln. Alle Pixel in diesem neuen Objekt sind per Voreinstellung auf schwarz mit einer Transparenz von 0 gesetzt. Oder anders ausgedrückt, jedes Pixel hat folgende Werte:
R = 0;//rot G = 0;//grün B = 0;//blau A = 0;//alpha
Neben der Methode createImageData() gibt es noch eine weitere Möglichkeit ein ImageData Objekt zu erstellen. Ihr könnt mit Hilfe der Methode getImageData() auch die Pixelinformationen aus einer Canvas auslesen.
Im folgenden Beispiel erstellen wir wieder eine Canvas. Aber im Gegensatz zum vorherigen Beispiel färben wir dieses Mal die Canvas per fillRect rot ein. Dementsprechend sind die RGBA Pixelinformationen die wir erhalten anders als im ersten Beispiel.
var canvas = document.createElement( 'canvas' );
canvas.width = 100;
canvas.height = 100;
document.body.appendChild( canvas );
var context = canvas.getContext( '2d' );
context.fillStyle = '#FF0000';
context.fillRect( 0, 0, 100, 100 );
var imageData = context.getImageData( 0, 0, 100, 100 );
Beispiel.
Nun haben wir ein ImageData Objekt erstellt das die Pixelinformationen der Canvas enthält.
Da wir unsere Canvas rot eingefärbt haben besitzen die Pixel nun folgende Werte:
R = 255;//rot G = 0;//grün B = 0;//blau A = 255;//alpha
Wir haben also zwei Methoden zur Auswahl mit denen wir ein ImageData Objekt erstellen können.
Die Methode createImageData() eignet sich vor allem dann, wenn man ein völlig neues und leeres „Bild“ als Grundlage haben möchte. Mit der Methode getImageData() hingegen kann man z.B. die Pixel eines geladenen Bildes auslesen und verändern.
Schauen wir uns nun die Werte genauer an die wir aus dem data Array auslesen können. Zunächst einmal die Grundlagen zu RGBA:
R = rot (ein Wert von 0 – 255) G = grün (ein Wert von 0 – 255) B = blau (ein Wert von 0 – 255) A = alpha (ein Wert von 0 – 255. 0 ist transparent und 255 voll sichtbar)
Im folgenden Beispiel lesen wir die Daten für das erste Pixel aus unserem per getImageData() erstellten imageData Objekt aus:
var r = imageData.data[ 0 ]; var g = imageData.data[ 1 ]; var b = imageData.data[ 2 ]; var a = imageData.data[ 3 ];
Wir wissen nun die Farbe des ersten Pixels (links oben) und können dem Beispiel entnehmen, dass die ersten vier Werte aus dem data Array, nämlich 0, 1, 2 und 3 ein Pixel repräsentieren. Folgerichtig besteht jedes Pixel aus vier Werten (r, g, b, a) und das hilft uns dabei wenn wir über das data Array iterieren wollen um an alle Pixelinformationen zu gelangen.
Um über das data Array zu iterieren nehmen wir eine übliche for-Schleife:
for ( var i = 0, i < imageData.data.length; i += 4 ) {
var r = imageData.data[ i ];
var g = imageData.data[ i + 1 ];
var b = imageData.data[ i + 2 ];
var a = imageData.data[ i + 3 ];
}
Auf diese Weise erhalten wir die RGBA Farbinformationen eines jeden im data Array gespeicherten Pixels. Der Trick hierbei besteht darin den Schlüssel i nicht um den Wert 1 zu erhöhen (i++), sondern um 4 (i += 4).
Denn, wie wir bereits wissen, besteht jedes Pixel aus 4 Werten (r, g, b, a) und somit kommen wir in jedem Schritt der for-Schleife an die Farbinformationen eines Pixels.
Hier gibt es noch ein sehr gutes Tutorial genau zu dem Thema.
Um das iterieren über das Array zu optimieren empfehle ich unbedingt 2 Änderungen an dem obigen Beispiel vorzunehmen. Zum einen speichern wir imgData.data in einer extra Variablen und die for-Schleife schreiben wir wie folgt auch etwas anders.
var data = imageData.data;
for ( var i = 0, l = data.length; i < l; i += 4 ) {
var r = data[ i ];
var g = data[ i + 1 ];
var b = data[ i + 2 ];
var a = data[ i + 3 ];
}
Auf diese Weise läuft das Auslesen der Pixelinformationen performanter ab. In den folgenden Beispielen setze ich voraus das data als extra Variable gespeichert ist.
Hier noch ein schöner Link mit weiteren Tipps zur Optimierung.
Auslesen ist das eine,
aber nun gehen wir einen Schritt weiter und verändern die Farbinformationen.
Im folgenden Beispiel geben wir allen Pixeln aus unserem data Array die RGBA Werte für die Farbe Grün.
Grün sieht so aus:
R = 0; G = 255; B = 0; A = 255;
Und nun ändern wir die Werte, die zuvor noch rot waren entsprechend in grün um:
for ( var i = 0, l = data.length; i < l; i += 4 ) {
data[ i ] = 0;
data[ i + 1 ] = 255;
data[ i + 2 ] = 0;
data[ i + 3 ] = 255;
}
Wir haben nun allen Pixeln die im data Array enthalten sind neue Werte zugewiesen.
Doch wieso wirkt sich diese Änderung nicht aus? Unsere Canvas ist immer noch rot und nicht grün.
Damit sich unsere „Pixel Manipulation“ auswirkt müssen wir noch einen Schritt durchführen:
context.putImageData( imageData, 0, 0 );
Mit der Methode putImageData() kopieren wir die von uns abgeänderten Pixelinformationen des ImageData Objekts zurück in die Canvas an die Position x = 0 und y = 0.
Und nun ist unsere Canvas nicht mehr rot, sondern grün.
Hier nun zusammengefasst das komplette Beispiel:
var canvas = document.createElement( 'canvas' );
canvas.width = 100;
canvas.height = 100;
document.body.appendChild( canvas );
var context = canvas.getContext( '2d' );
context.fillStyle = '#FF0000';
context.fillRect( 0, 0, 100, 100 );
var imageData = context.getImageData( 0, 0, 100, 100 );
var data = imageData.data;
for ( var i = 0, l = data.length; i < l; i += 4 ) {
data[ i ] = 0;
data[ i + 1 ] = 255;
data[ i + 2 ] = 0;
data[ i + 3 ] = 255;
}
context.putImageData( imageData, 0, 0 );
Beispiel in JSFiddle.
Wir verfügen nun über die Grundlagen und wissen wie wir Pixelinformationen auslesen und verändern können.
Natürlich macht es wenig Sinn eine komplette Fläche, also eine komplette Canvas auf diese Weise von einer Farbe in eine andere umzufärben. Dafür gibt es auch andere Methoden für die wir nicht einmal Zugriff auf die Pixel benötigen. Um aber beispielhaft zu erklären wie das ganze grundliegend funktioniert ist es dann eben doch sinnvoll.
Folgend ein etwas anderes Beispiel, welches man ohne den Zugriff auf die Pixel so nicht umsetzen könnte. Wir geben jetzt jedem einzelnen Pixel aus dem data Array per random einen zufälligen Farbwert:
var canvas = document.createElement( 'canvas' );
canvas.width = 100;
canvas.height = 100;
document.body.appendChild( canvas );
var context = canvas.getContext( '2d' );
var imageData = context.getImageData( 0, 0, 100, 100 );
var data = imageData.data;
for ( var i = 0, l = data.length; i < l; i += 4 ) {
data[ i ] = Math.floor( Math.random() * 255 );
data[ i + 1 ] = Math.floor( Math.random() * 255 );
data[ i + 2 ] = Math.floor( Math.random() * 255 );
data[ i + 3 ] = 255;
}
context.putImageData( imageData, 0, 0 );
Beispiel in JSFiddle.
Und hier noch drei weitere JSFiddle Beispiele. Dieses Mal laden wir ein externes Bild und zeichnen dieses, nachdem es geladen worden ist, per drawImage in die Canvas und verändern dessen Pixelinformationen.
Beispiel 1: Farben invertieren
Beispiel 2: Ein Graustufenbild erzeugen
Beispiel 3: Per Schwellenwertverfahren Farbwerte zufällig segmentieren
Hier noch ein externes Tutorial zu diesem Thema, das etwas tiefer in die Materie Filter eintaucht und sehr schöne Beispiele beinhaltet.
Anhand dieser Beispiele sollte klar werden wie mächtig der Zugriff auf die Pixel sein kann.
Achtung: Wenn wir mit der Methode drawImage ein Bild in die Canvas zeichnen und Zugriff auf die Pixel haben wollen, dann muss sich dieses Bild auf dem gleichen Server befinden auf dem auch das JavaScript ausgeführt wird! Ist das nicht der Fall wird die Konsole eures Browsers eine Fehlermeldung ausgeben und der Zugriff auf die Pixel wird verweigert:
SecurityError: The operation is insecure.
Man kann das umgehen. Aber der Weg ist steinig und nicht in jedem Fall durchzuführen.
Das Stichwort hierbei lautet CORS. Vereinfacht ausgedrückt: Der Besitzer des Bildes muss euch den Zugriff auf das Bild erlauben. Wie man das macht wird hier beschrieben.
Wenn ihr dann ein solches Bild von einem anderen Server laden wollt, dann müsst ihr unbedingt die folgende Zeile in eurem Script ergänzen:
image.crossOrigin = "anonymous";
Wichtig hierbei. Diese extra Zeile muss vor der Zeile eingefügt werden in der ihr den src-Pfad des Bildes angebt. Hier ein Beispiel wie ihr ein solches Bild laden könnt:
var image = document.createElement( 'IMG' );
image.onload = function () {
//Image ready
}
image.crossOrigin = "anonymous";
image.src = 'http://www.beispiel.de/seinBild.jpg';
Und hier noch einmal zum Vergleich ein Beispiel wie ihr ein Bild vom gleichen Server laden könnt:
var image = document.createElement( 'IMG' );
image.onload = function () {
//Image ready
}
image.src = 'deinBild.jpg';
Das aber nur kurz zur Erklärung. Nicht das ihr euch wundert, wenn ihr ein Bild von einem anderen/fremden Server ladet und dann die hier gelernten Inhalte nicht funktionieren.
Kommen wir nun aber zurück zum eigentlichen Inhalt dieses Tutorials.
Bisher haben wir einfach immer nur alle Pixel eines Bildes ausgelesen und/oder verändert. Was aber wenn wir gezielt einzelne Pixel ansprechen/auslesen wollen?
Dazu brauchen wir zwei Funktionen/Methoden, die es uns erlauben anhand von XY-Koordinaten einzelne Pixel aus dem data Array anzusprechen.
Fangen wir damit an einzelne Pixel aus dem data Array per XY-Koordinaten zu verändern.
Das Problem. Wir bekommen per data Array nur über den Index Zugriff auf die einzelnen Werte.
Daher müssen wir einen kleinen Trick anwenden um unsere X und Y Werte in den Index umzurechnen. Eigentlich ganz einfach. Und so wird’s gemacht:
var i = ( x + y * imageData.width ) * 4;//i = index
Verpacken wir das jetzt in einer Funktion, die als Paramater X, Y und ein Objekt (c) mit den Farbwerten RGBA entgegen nimmt, kommt das dabei raus:
function setPixel( x, y, c ) {
var i = ( x + y * imageData.width ) * 4;
data[ i ] = c.r;
data[ i + 1 ] = c.g;
data[ i + 2 ] = c.b;
data[ i + 3 ] = c.a;
};
Wir haben nun eine Funktion, die es uns erlaubt die Farbwerte eines per XY-Koordinaten ausgewählten Pixels zu verändern.
Dabei ist zu beachten, dass die X und Y Werte ganzzahlige Werte ohne Komma und Dezimalstellen sind. Schließlich gibt es keine Pixel auf Koordinaten mit Komma. Es gilt also, wenn vorhanden, Fließkommazahlen (Float) vorab in Integer umzuwandeln. Vereinfacht ausgedrückt: Bevor wir die Funktion setPixel aufrufen, müssen wir dafür sorgen, dass die XY-Koordinaten keine Kommas mehr enthalten. Es gibt mehrere Wege das zu tun. Ich zeige hier 2 Möglichkeiten auf, wobei ich immer die zweite Variante verwende.
Variante 1 mit Math.floor():
var x = 19.123425235;
var y = 45.534534213;
var c = { r:0, g:0, b:0, a:255 };
function setPixel( x, y, c ) {
var i = ( x + y * imageData.width ) * 4;
data[ i ] = c.r;
data[ i + 1 ] = c.g;
data[ i + 2 ] = c.b;
data[ i + 3 ] = c.a;
};
setPixel( Math.floor( x ), Math.floor( y ), c );
Variante 2 mit Bitwise OR operator:
var x = 19.123425235;
var y = 45.534534213;
var c = { r:0, g:0, b:0, a:255 };
function setPixel( x, y, c ) {
var i = ( x + y * imageData.width ) * 4;
data[ i ] = c.r;
data[ i + 1 ] = c.g;
data[ i + 2 ] = c.b;
data[ i + 3 ] = c.a;
};
setPixel( x | 0, y | 0, c );
In beiden Beispielen färben wir jeweils ein Pixel schwarz ein. Daher auch die Variable c, bzw. das Objekt c mit den Werten für RGBA:
var c = { r:0, g:0, b:0, a:255 };
Wie wir sehen beinhalten die Variablen für die XY-Koordinaten Fließkommazahlen. Einfach nur um das mit der Umwandlung zu verdeutlichen. Würden wir an dieser Stelle keine Umwandlung vornehmen, dann würden die hier gezeigten Beispiele versagen und die Funktion setPixel wäre nicht mehr in der Lage aus den XY-Koordinaten den Index für das Array zu berechnen.
Ich bevorzuge die Variante 2 mit dem Bitwise OR operator, weil das, wenn man sehr viele Pixel Zugriffe auf einmal hat, einfach ein Stück weit performanter läuft als mit Math.floor().
Und noch eine Kleinigkeit gilt es bei der Funktion setPixel zu beachten. Führt ihr Berechnungen durch, bei denen es vorkommen kann, dass die XY-Koordinaten außerhalb des Bereichs der Canvas liegen, dann müsst/solltet ihr durch eine zusätzliche If-Bedingung sicherstellen, dass diese nicht gezeichnet werden. Das führt sonst zu komischen, manchmal auch interessanten, aber bestimmt nicht gewollten Effekten. Hier ein Beispiel dafür, mit der Annahme, dass unsere Canvas eine Breite und Höhe von 100 Pixeln hat und das unser Pixel außerhalb davon liegt:
var x = -19.123425235;
var y = 450.53453213;
var c = { r:0, g:0, b:0, a:255 };
function setPixel( x, y, c ) {
var i = ( x + y * imageData.width ) * 4;
data[ i ] = c.r;
data[ i + 1 ] = c.g;
data[ i + 2 ] = c.b;
data[ i + 3 ] = c.a;
};
if ( x > 0 && x <= 100 && y > 0 && y <= 100 )
setPixel( x | 0, y | 0, c );
Hier noch einmal als Beispiel in JSFiddle mit Koordinaten innerhalb der Canvas.
Jetzt, wo wir wissen wie man die Farbwerte eines Pixels verändert, soll es nun darum gehen die Farbinformationen bestimmter Pixel auszulesen. Die folgende Funktion nimmt die Parameter X und Y entgegen und gibt ein Objekt zurück mit den Werten RGBA:
function getPixel( x, y ) {
var i = ( x + y * imageData.width ) * 4;
return { r:data[ i ],
g:data[ i + 1 ],
b:data[ i + 2 ],
a:data[ i + 3 ] }
};
In diesem Beispiel in JSFiddle lesen wir per Zufall den Farbwert eines Pixels aus.
Mit den beiden Methoden setPixel und getPixel haben wir nun ganz komfortabel Zugriff auf die einzelnen Pixel der Canvas. Von hier an stehen uns im Prinzip alle Türen offen.
Folgend ein paar JSFiddle Beispiele mit denen wir per XY-Koordinaten die Pixel der Canvas verändern und somit Bilder aus Code generieren.
In einem weiteren Tutorial, das auf diesem aufbauen wird, werde ich dann erklären wie ihr die dritte Dimension und Partikel (wie hier bei diversen Beispielen schon gezeigt) auf eure Canvas bringt.
Hier noch zum Abschluss eine kleine aber feine Auswahl an deutschsprachigen Tutorials rund um dieses Thema:
- Pixelmanipulation
- Canvas - mit Pixeln arbeiten [HTML5+JS]
- Canvas: Bitmaps analysieren und manipulieren
Ich hoffe das Tutorial hat euch gefallen. Falls noch irgendetwas unklar sein sollte hinterlasst einfach einen Kommentar oder schreibt mir. Ich würde mich freuen wenn ihr wieder vorbei schaut zu Teil 2.
Abonnieren
Posts (Atom)

