Moderne Browser unterstützen das Abspielen von Videos über das <video>-Element. Die meisten Browser haben auch Zugriff auf Webcams über die MediaDevices.getUserMedia() API. Aber selbst mit diesen beiden Dingen kombiniert, können wir diese Pixel nicht wirklich direkt zugreifen und manipulieren.
Glücklicherweise bieten Browser eine Canvas API, die es uns ermöglicht, Grafiken mit JavaScript zu zeichnen. Wir können tatsächlich Bilder aus dem Video selbst auf das <canvas> zeichnen, was uns die Möglichkeit gibt, diese Pixel zu manipulieren und damit zu spielen.
Alles, was Sie hier über die Manipulation von Pixeln lernen, wird Ihnen eine Grundlage für die Arbeit mit Bildern und Videos jeder Art und aus jeder Quelle geben, nicht nur mit Canvas.
Bild zum Canvas hinzufügen
Bevor wir mit dem Video beginnen, betrachten wir das Hinzufügen eines Bildes zum Canvas.
<img id="SourceImage" src="image.jpg">
<div class="video-container"></div>
Wir haben ein Bildelement erstellt, das das Bild darstellt, das auf den Canvas gezeichnet wird. Alternativ könnten wir das Image-Objekt in JavaScript verwenden.
var canvas;
var context;
function init() {
var image = document.getElementById('SourceImage');
canvas = document.getElementById('Canvas');
context = canvas.getContext('2d');
drawImage(image);
// Or
// var image = new Image();
// image.onload = function () {
// drawImage(image);
// }
// image.src = 'image.jpg';
}
function drawImage(image) {
// Set the canvas the same width and height of the image
canvas.width = image.width;
canvas.height = image.height;
context.drawImage(image, 0, 0);
}
window.addEventListener('load', init);
Der obige Code zeichnet das gesamte Bild auf den Canvas.
Jetzt können wir anfangen, mit diesen Pixeln zu spielen!
Bilddaten aktualisieren
Die Bilddaten auf dem Canvas ermöglichen es uns, die Pixel zu manipulieren und zu ändern.
Die Eigenschaft data ist ein ImageData-Objekt mit drei Eigenschaften – width, height und data –, die alle diese Dinge basierend auf dem ursprünglichen Bild darstellen. Alle diese Eigenschaften sind readonly. Diejenige, die uns interessiert, ist data, ein eindimensionales Array, das durch ein Uint8ClampedArray-Objekt dargestellt wird und die Daten jedes Pixels im RGBA-Format enthält.
Obwohl die Eigenschaft data readonly ist, bedeutet das nicht, dass wir ihren Wert nicht ändern können. Es bedeutet, dass wir dieser Eigenschaft kein anderes Array zuweisen können.
// Get the canvas image data
var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
image.data = new Uint8ClampedArray(); // WRONG
image.data[1] = 0; // CORRECT
Welche Werte repräsentiert das Uint8ClampedArray-Objekt, fragen Sie sich vielleicht. Hier ist die Beschreibung von MDN:
Das typisierte Array
Uint8ClampedArrayrepräsentiert ein Array von 8-Bit-Ganzzahlen ohne Vorzeichen, die auf 0-255 begrenzt sind; wenn Sie einen Wert außerhalb des Bereichs [0,255] angeben, wird stattdessen 0 oder 255 gesetzt; wenn Sie eine nicht ganzzahlige Zahl angeben, wird die nächste ganze Zahl gesetzt. Der Inhalt wird mit0initialisiert. Sobald es erstellt ist, können Sie auf Elemente im Array über die Methoden des Objekts oder über die Standard-Array-Indexsyntax (d. h. über die Klammernotation) zugreifen.
Kurz gesagt, dieses Array speichert Werte von 0 bis 255 in jeder Position, was es zur perfekten Lösung für das RGBA-Format macht, da jeder Teil durch Werte von 0 bis 255 dargestellt wird.
RGBA-Farben
Farben können im RGBA-Format dargestellt werden, das eine Kombination aus Rot (Red), Grün (Green) und Blau (Blue) ist. Das A steht für den Alphawert, der die Deckkraft der Farbe angibt.
Jede Position im Array repräsentiert einen Farbkanalwert (Pixel).
- 1. Position ist der Rotwert
- 2. Position ist der Grünwert
- 3. Position ist der Blauwert
- 4. Position ist der Alpha-Wert
- 5. Position ist der Rotwert des nächsten Pixels
- 6. Position ist der Grünwert des nächsten Pixels
- 7. Position ist der Blauwert des nächsten Pixels
- 8. Position ist der Alpha-Wert des nächsten Pixels
- Und so weiter…
Wenn Sie ein 2×2-Bild haben, dann haben wir ein Array mit 16 Positionen (2×2 Pixel × 4 Werte pro Pixel).

Das Array wird wie unten gezeigt dargestellt
// RED GREEN BLUE WHITE
[ 255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255, 255, 255, 255, 255]
Pixeldaten ändern
Eine der schnellsten Dinge, die wir tun können, ist, alle Pixel weiß zu machen, indem wir alle RGBA-Werte auf 255 ändern.
// Use a button to trigger the "effect"
var button = document.getElementById('Button');
button.addEventListener('click', onClick);
function changeToWhite(data) {
for (var i = 0; i < data.length; i++) {
data[i] = 255;
}
}
function onClick() {
var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
changeToWhite(imageData.data);
// Update the canvas with the new data
context.putImageData(imageData, 0, 0);
}
Die data wird als Referenz übergeben, was bedeutet, dass jede Änderung, die wir daran vornehmen, den Wert des übergebenen Arguments ändert.
Farben invertieren
Ein schöner Effekt, der nicht viel Berechnung erfordert, ist das Invertieren der Farben eines Bildes.
Das Invertieren eines Farbwertes kann mit dem XOR-Operator (^) oder dieser Formel 255 - Wert erfolgen (Wert muss zwischen 0 und 255 liegen).
function invertColors(data) {
for (var i = 0; i < data.length; i+= 4) {
data[i] = data[i] ^ 255; // Invert Red
data[i+1] = data[i+1] ^ 255; // Invert Green
data[i+2] = data[i+2] ^ 255; // Invert Blue
}
}
function onClick() {
var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
invertColors(imageData.data);
// Update the canvas with the new data
context.putImageData(imageData, 0, 0);
}
Wir erhöhen die Schleife um 4 statt um 1, wie wir es zuvor getan haben, damit wir von Pixel zu Pixel gehen und jedes Element im Array ausfüllen.
Der Alpha-Wert hat keinen Einfluss auf das Invertieren von Farben, also überspringen wir ihn.
Helligkeit und Kontrast
Die Helligkeit eines Bildes kann mit der folgenden Formel angepasst werden: neuerWert = aktuellerWert + 255 * (helligkeit / 100).
helligkeitmuss zwischen -100 und 100 liegenaktuellerWertist der aktuelle Lichtwert von Rot, Grün oder Blau.neuerWertist das Ergebnis des aktuellen Farblichts plushelligkeit
Die Anpassung des Kontrasts eines Bildes kann mit dieser Formel erfolgen.
factor = (259 * (contrast + 255)) / (255 * (259 - contrast))
color = GetPixelColor(x, y)
newRed = Truncate(factor * (Red(color) - 128) + 128)
newGreen = Truncate(factor * (Green(color) - 128) + 128)
newBlue = Truncate(factor * (Blue(color) - 128) + 128)
Die Hauptberechnung besteht darin, den Kontrastfaktor zu erhalten, der auf jeden Farbwert angewendet wird. Truncate ist eine Funktion, die sicherstellt, dass der Wert zwischen 0 und 255 bleibt.
Schreiben wir diese Funktionen in JavaScript.
function applyBrightness(data, brightness) {
for (var i = 0; i < data.length; i+= 4) {
data[i] += 255 * (brightness / 100);
data[i+1] += 255 * (brightness / 100);
data[i+2] += 255 * (brightness / 100);
}
}
function truncateColor(value) {
if (value < 0) {
value = 0;
} else if (value > 255) {
value = 255;
}
return value;
}
function applyContrast(data, contrast) {
var factor = (259.0 * (contrast + 255.0)) / (255.0 * (259.0 - contrast));
for (var i = 0; i < data.length; i+= 4) {
data[i] = truncateColor(factor * (data[i] - 128.0) + 128.0);
data[i+1] = truncateColor(factor * (data[i+1] - 128.0) + 128.0);
data[i+2] = truncateColor(factor * (data[i+2] - 128.0) + 128.0);
}
}
In diesem Fall benötigen Sie die Funktion truncateColor nicht, da Uint8ClampedArray diese Werte kürzt, aber um des Algorithmus willen haben wir sie hinzugefügt.
Eine Sache, die man bedenken sollte, ist, dass, wenn Sie eine Helligkeit oder einen Kontrast anwenden, es keinen Weg zurück zum vorherigen Zustand gibt, da die Bilddaten überschrieben werden. Die ursprünglichen Bilddaten müssen separat gespeichert werden, wenn wir zum ursprünglichen Zustand zurückkehren möchten. Wenn die Bildvariable für andere Funktionen zugänglich bleibt, ist das hilfreich, da Sie dieses Bild stattdessen verwenden können, um den Canvas mit dem Originalbild neu zu zeichnen.
var image = document.getElementById('SourceImage');
function redrawImage() {
context.drawImage(image, 0, 0);
}
Videos verwenden
Um mit Videos zu arbeiten, nehmen wir unser anfängliches Skript und HTML-Code und nehmen einige kleine Änderungen vor.
HTML
Ersetzen Sie das Bildelement durch ein Videoelement, indem Sie diese Zeile ersetzen
<img id="SourceImage" src="image.jpg">
…mit diesem.
<video id="SourceVideo" src="video.mp4" width="300" height="150"></video>
JavaScript
Ersetzen Sie diese Zeile
var image = document.getElementById('SourceImage');
…mit diesem.
var video = document.getElementById('SourceVideo');
Um mit dem Video zu arbeiten, müssen wir warten, bis das Video abgespielt werden kann.
video.addEventListener('canplay', function () {
// Set the canvas the same width and height of the video
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
// Play the video
video.play();
// start drawing the frames
drawFrame(video);
});
Das Ereignis canplay wird ausgelöst, wenn genügend Daten verfügbar sind, dass das Medium abgespielt werden kann, zumindest für ein paar Frames.
Wir können kein Video auf dem Canvas sehen, da wir nur den ersten Frame anzeigen. Wir müssen drawFrame alle n Millisekunden ausführen, um mit der Bildrate des Videos Schritt zu halten.
Innerhalb von drawFrame rufen wir drawFrame erneut alle 10 ms auf.
function drawFrame(video) {
context.drawImage(video, 0, 0);
setTimeout(function () {
drawFrame(video);
}, 10);
}
Nachdem wir drawFrame ausgeführt haben, erstellen wir eine Schleife, die drawFrame alle 10 ms ausführt – das ist genug Zeit, um das Video im Canvas synchron zu halten.
Effekt zum Video hinzufügen
Wir können dieselbe Funktion verwenden, die wir zuvor zum Invertieren von Farben erstellt haben.
function invertColors(data) {
for (var i = 0; i < data.length; i+= 4) {
data[i] = data[i] ^ 255; // Invert Red
data[i+1] = data[i+1] ^ 255; // Invert Green
data[i+2] = data[i+2] ^ 255; // Invert Blue
}
}
Und sie in die Funktion drawFrame einfügen.
function drawFrame(video) {
context.drawImage(video, 0, 0);
var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
invertColors(imageData.data);
context.putImageData(imageData, 0, 0);
setTimeout(function () {
drawFrame(video);
}, 10);
}
Wir können einen Button hinzufügen und die Effekte umschalten.
function drawFrame(video) {
context.drawImage(video, 0, 0);
if (applyEffect) {
var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
invertColors(imageData.data);
context.putImageData(imageData, 0, 0);
}
setTimeout(function () {
drawFrame(video);
}, 10);
}
Kamera verwenden
Wir behalten denselben Code, den wir für Videos verwenden, wobei der einzige Unterschied darin besteht, dass wir den Videostream von einer Datei zum Kamerastream ändern, indem wir MediaDevices.getUserMedia verwenden.
MediaDevices.getUserMedia ist die neue API, die die vorherige API Navigator.getUserMedia ablöst. Es gibt immer noch Browserunterstützung für die alte Version und einige Browser unterstützen die neue Version nicht, und wir müssen auf Polyfills zurückgreifen, um sicherzustellen, dass der Browser eine davon unterstützt.
Entfernen Sie zuerst das src-Attribut aus dem Videoelement.
<video id="SourceVideo" width="300" height="150"></video>
// Set the source of the video to the camera stream
function initCamera(stream) {
video.srcObject = stream;
}
if (navigator.mediaDevices.getUserMedia) {
navigator.mediaDevices.getUserMedia({video: true, audio: false})
.then(initCamera)
.catch(console.error)
);
}
Effekte
Alles, was wir bisher behandelt haben, ist die Grundlage, die wir brauchen, um verschiedene Effekte auf ein Video oder Bild anzuwenden. Es gibt viele verschiedene Effekte, die wir anwenden können, indem wir jede Farbe unabhängig transformieren.
Graustufen
Das Konvertieren einer Farbe in Graustufen kann auf verschiedene Arten mit unterschiedlichen Formeln/Techniken erfolgen. Um nicht zu tief in das Thema einzusteigen, zeige ich Ihnen fünf Formeln, die auf dem GIMP Entsättigungswerkzeug und Luma basieren.
Gray = 0.21R + 0.72G + 0.07B // Luminosity
Gray = (R + G + B) ÷ 3 // Average Brightness
Gray = 0.299R + 0.587G + 0.114B // rec601 standard
Gray = 0.2126R + 0.7152G + 0.0722B // ITU-R BT.709 standard
Gray = 0.2627R + 0.6780G + 0.0593B // ITU-R BT.2100 standard
Was wir mit diesen Formeln finden wollen, ist die Helligkeitsintensität jedes Farb-Pixels. Der Wert reicht von 0 (Schwarz) bis 255 (Weiß). Diese Werte erzeugen einen Graustufen-Effekt (Schwarzweiß).
Das bedeutet, dass die hellste Farbe am nächsten bei 255 und die dunkelste Farbe am nächsten bei 0 liegt.
Duotone-Effekte
Der Unterschied zwischen Duotone-Effekt und Graustufen-Effekt sind die beiden verwendeten Farben. Bei Graustufen haben Sie einen Verlauf von Schwarz zu Weiß, während Sie bei Duotone einen Verlauf von jeder Farbe zu jeder anderen Farbe haben können, z. B. Blau zu Pink.
Mithilfe des Intensitätswerts der Graustufen können wir dies aus den Gradientenwerten ersetzen.
Wir müssen einen Farbverlauf von ColorA zu ColorB erstellen.
function createGradient(colorA, colorB) {
// Values of the gradient from colorA to colorB
var gradient = [];
// the maximum color value is 255
var maxValue = 255;
// Convert the hex color values to RGB object
var from = getRGBColor(colorA);
var to = getRGBColor(colorB);
// Creates 256 colors from Color A to Color B
for (var i = 0; i <= maxValue; i++) {
// IntensityB will go from 0 to 255
// IntensityA will go from 255 to 0
// IntensityA will decrease intensity while instensityB will increase
// What this means is that ColorA will start solid and slowly transform into ColorB
// If you look at it in other way the transparency of color A will increase and the transparency of color B will decrease
var intensityB = i;
var intensityA = maxValue - intensityB;
// The formula below combines the two color based on their intensity
// (IntensityA * ColorA + IntensityB * ColorB) / maxValue
gradient[i] = {
r: (intensityA*from.r + intensityB*to.r) / maxValue,
g: (intensityA*from.g + intensityB*to.g) / maxValue,
b: (intensityA*from.b + intensityB*to.b) / maxValue
};
}
return gradient;
}
// Helper function to convert 6digit hex values to a RGB color object
function getRGBColor(hex)
{
var colorValue;
if (hex[0] === '#') {
hex = hex.substr(1);
}
colorValue = parseInt(hex, 16);
return {
r: colorValue >> 16,
g: (colorValue >> 8) & 255,
b: colorValue & 255
}
}
Kurz gesagt, wir erstellen ein Array von Farbwerten von Farbe A, die die Intensität verringert, während sie zu Farbe B geht und ihre Intensität erhöht.

#0096ff bis #ff00f0
var gradients = [
{r: 32, g: 144, b: 254},
{r: 41, g: 125, b: 253},
{r: 65, g: 112, b: 251},
{r: 91, g: 96, b: 250},
{r: 118, g: 81, b: 248},
{r: 145, g: 65, b: 246},
{r: 172, g: 49, b: 245},
{r: 197, g: 34, b: 244},
{r: 220, g: 21, b: 242},
{r: 241, g: 22, b: 242},
];
Oben sehen Sie ein Beispiel für einen Verlauf von 10 Farbwerten von #0096ff bis #ff00f0.

Nun, da wir die Graustufen-Darstellung des Bildes haben, können wir sie verwenden, um sie auf die Duotone-Gradientenwerte abzubilden.
Der Duotone-Verlauf hat 256 Farben, während die Graustufen ebenfalls 256 Farben haben, die von Schwarz (0) bis Weiß (255) reichen. Das bedeutet, dass ein Graustufen-Farbwert einem Gradienten-Elementindex zugeordnet wird.
var gradientColors = createGradient('#0096ff', '#ff00f0');
var imageData = context.getImageData(0, 0, canvas.width, canvas.height);
applyGradient(imageData.data);
for (var i = 0; i < data.length; i += 4) {
// Get the each channel color value
var redValue = data[i];
var greenValue = data[i+1];
var blueValue = data[i+2];
// Mapping the color values to the gradient index
// Replacing the grayscale color value with a color for the duotone gradient
data[i] = gradientColors[redValue].r;
data[i+1] = gradientColors[greenValue].g;
data[i+2] = gradientColors[blueValue].b;
data[i+3] = 255;
}
Fazit
Dieses Thema kann noch weiter vertieft oder mehr Effekte erklärt werden. Ihre Hausaufgabe ist es, verschiedene Algorithmen zu finden, die Sie auf diese Gerüstbeispiele anwenden können.
Zu wissen, wie Pixel auf einem Canvas strukturiert sind, ermöglicht es Ihnen, eine unbegrenzte Anzahl von Effekten zu erstellen, wie Sepia, Farbüberblendung, einen Greenscreen-Effekt, Bildflackern/Glitching usw.
Sie können sogar Effekte im laufenden Betrieb erstellen, ohne ein Bild oder Video zu verwenden:
Gibt es einen Grund, setTimeout anstelle von requestAnimationFrame zu verwenden?
Überhaupt nicht,
requestAnimationFrameliefert bessere Ergebnisse. Die Beispiele müssen aktualisiert werden!Ich empfehle dringend, http://www.pixijs.com/ zu verwenden, um die Leistung zu steigern und die Bildrate zu erhöhen. Sehen Sie sich diese Demo an: https://github.com/giorgiobeggiora/slides-html5-video-filters-2014
Ja, ich stimme zu
Ich habe jeden Schritt befolgt.
Danke für die Tutorials.
Tolles Tutorial! Danke! Ich habe jetzt ein viel besseres Verständnis für Canvas. Ich habe mich mit einigen Canvas-Funktionen in einer Webanwendung, die ich entwickle, auseinandergesetzt. Das wirft ein neues Licht auf mein Verständnis. Ich freue mich darauf, mehr Canvas-Tutorials von Welling zu sehen. Sehr gut gemacht!