Les navigateurs modernes prennent en charge la lecture vidéo via l'élément <video>
. La plupart des navigateurs peuvent également accéder à la caméra via l'API MediaDevices.getUserMedia(). Mais même si ces deux choses sont combinées, nous ne pouvons pas accéder directement à ces pixels et les manipuler.
Heureusement, les navigateurs disposent d'une API Canvas qui nous permet de dessiner des graphiques à l'aide de JavaScript. Nous pouvons en fait dessiner des images sur <canvas>
à partir de la vidéo elle-même, ce qui nous permet de manipuler et d'afficher ces pixels.
Ce que vous apprendrez ici sur la façon de manipuler les pixels vous fournira les bases pour travailler avec des images et des vidéos de tout type ou source, et pas seulement avec du canevas.
Ajouter une image au canevasAvant de commencer la vidéo, voyons comment ajouter une image au canevas.
<img src><div> <canvas id=Canvas class=video></canvas></div>
Nous créons un élément image pour représenter l'image à dessiner sur la toile. Alternativement, nous pouvons utiliser l'objet Image en JavaScript.
var toile;var contexte;fonction init() { var image = document.getElementById('SourceImage'); toile = document.getElementById('Canvas'); contexte = toile.getContext('2d'); // Ou // var image = new Image(); // image.onload = function () { // drawImage(image); 'image.jpg';}function drawImage(image) { // Définit le canevas à la même largeur et hauteur que l'image canvas.width = image.width; canvas.height = image.height; 0);}window.addEventListener('load', init);
Le code ci-dessus dessine l'image entière sur le canevas.
Découvrez l'image Paint sur toile de Welling Guzman (@wellingguzman) sur CodePen.
Nous pouvons maintenant commencer à jouer avec ces pixels !
Mettre à jour les données d'imageLes données d'image sur le canevas nous permettent de manipuler et de modifier les pixels.
L'attribut data est un objet ImageData qui possède trois propriétés - width, height et data/qui représentent toutes quelque chose basé sur l'image originale. Toutes ces propriétés sont en lecture seule. Ce qui nous intéresse, ce sont les données, un tableau unidimensionnel représenté par un objet Uint8ClampedArray contenant des données pour chaque pixel au format RGBA.
Même si une propriété de données est en lecture seule, cela ne signifie pas que nous ne pouvons pas modifier sa valeur. Cela signifie que nous ne pouvons pas attribuer un autre tableau à cette propriété.
// Récupère la variable de données de l'image du canevas imageData = context.getImageData(0, 0, canvas.width, canvas.height);image.data = new Uint8ClampedArray(); // WRONGimage.data[1] = 0;
Vous vous demandez peut-être quelle valeur représente un objet Uint8ClampedArray ? Voici la description de MDN :
Le tableau de type Uint8ClampedArray représente un tableau d'entiers non signés de 8 bits limités à 0-255 ; si vous spécifiez une valeur en dehors de la plage [0,255], 0 ou 255 sera défini si vous spécifiez un nombre non entier, le plus proche ; Un nombre entier sera défini. Le contenu est initialisé à 0. Une fois établis, les éléments du tableau peuvent être référencés à l'aide des méthodes de l'objet ou à l'aide de la syntaxe d'indexation de tableau standard (c'est-à-dire en utilisant la notation entre crochets).
Bref, ce tableau stocke des valeurs allant de 0 à 255 à chaque emplacement, ce qui en fait une solution parfaite pour le format RGBA puisque chaque partie est représentée par une valeur de 0 à 255.
Couleur RVBALa couleur peut être représentée au format RGBA, qui est une combinaison de rouge, de vert et de bleu. A représente la valeur alpha de l'opacité de la couleur.
Chaque position dans le tableau représente une valeur de canal de couleur (pixel).
Si vous avez une image 2x2, alors nous avons un tableau de 16 bits (2x2 pixels x 4 valeurs chacun).
Image 2x2 réduite
Le tableau ressemblera à ceci :
// ROUGE VERT BLEU BLANC[255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255, 255, 255, 255, 255]Modifier les données de pixels
L'une des choses les plus rapides que nous puissions faire est de définir tous les pixels en blanc en modifiant toutes les valeurs RGBA à 255.
// Utilisez un bouton pour déclencher l'effetvar 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); // Mettre à jour le canevas avec les nouvelles données context.putImageData(imageData, 0, 0);}
Les données seront transmises comme référence, ce qui signifie que toute modification que nous y apporterons modifiera la valeur du paramètre transmis.
Inverser les couleursUn effet intéressant qui ne nécessite pas trop de calculs consiste à inverser les couleurs d'une image.
Les valeurs de couleur peuvent être inversées à l'aide de l'opérateur XOR (^) ou de cette formule 255 - valeur (la valeur doit être comprise entre 0 et 255).
function invertColors(data) { for (var i = 0; i < data.length; i+= 4) { data[i] = data[i] ^ 255; // Inverser les données rouges[i+1] = data[i +1] ^ 255; // Inverser les données vertes[i+2] = data[i+2] ^ 255; // Inverser le bleu }}function onClick() { var imageData = context.getImageData(0, 0, canvas.width, canvas.height); invertColors(imageData.data); // Mettre à jour le canevas avec les nouvelles données context.putImageData(imageData, 0, 0);}
Nous incrémentons la boucle de 4 au lieu de 1 comme nous le faisions auparavant, afin de pouvoir remplir 4 éléments du tableau de pixel en pixel, chaque pixel.
La valeur alpha n'a aucun effet sur l'inversion des couleurs, nous l'ignorons donc.
Luminosité et contrasteLa luminosité d'une image peut être ajustée à l'aide de la formule suivante : newValue = currentValue + 255 * (luminosité / 100).
factor = (259 * (contraste + 255)) / (255 * (259 - contraste))color = GetPixelColor(x, y)newRed = Truncate(factor * (Red(color) - 128) + 128)newGreen = Truncate( facteur * (Vert (couleur) - 128) + 128)nouveauBleu = Tronquer(facteur * (Bleu(couleur) - 128) + 128)
Le calcul principal consiste à obtenir le facteur de contraste qui sera appliqué à chaque valeur de couleur. La troncature est une fonction qui garantit que la valeur reste comprise entre 0 et 255.
Écrivons ces fonctions en JavaScript :
function applyBrightness(data, luminosité) { for (var i = 0; i < data.length; i+= 4) { data[i] += 255 * (luminosité / 100) ; (luminosité / 100); data[i+2] += 255 * (luminosité / 100 }}fonction truncateColor(value) { if (valeur < 0) { valeur = 0; } else if (valeur > 255) { valeur = 255; } valeur de retour ;}fonction applyContrast(données, contraste) { var factor = (259,0 * (contraste + 255,0)) / ( 255,0 * (259,0 - contraste)); pour (var i = 0; i < data.length; i+= 4) { data[i] = truncateColor(facteur * (données[i] - 128,0) + 128,0); données[i+1] = truncateColor(facteur * (données[i+1] - 128,0) + 128,0); facteur * (données[i+2] - 128,0) + 128,0 }} ;
Dans ce cas, vous n'avez pas besoin de la fonction truncateColor car Uint8ClampedArray tronque les valeurs, mais de traduire l'algorithme que nous y avons ajouté.
Une chose à retenir est que si vous appliquez de la luminosité ou du contraste, les données de l'image sont écrasées et ne peuvent pas revenir à leur état précédent. Si nous souhaitons réinitialiser l'état d'origine, les données d'image originales doivent être stockées séparément pour référence. Garder la variable image accessible à d'autres fonctions sera utile car vous pouvez utiliser l'image pour redessiner le canevas et l'image d'origine.
var image = document.getElementById('SourceImage'); function redrawImage() { context.drawImage(image, 0, 0);}Utiliser la vidéo
Pour que cela fonctionne pour la vidéo, nous prendrons notre script d'image et notre code HTML initiaux et apporterons quelques modifications mineures.
HTMLModifiez l'élément Image de l'élément vidéo en remplaçant la ligne suivante :
<img src>
...avec ça :
<vidéo src></vidéo>
Javascript
Remplacez cette ligne :
var image = document.getElementById('SourceImage');
...ajoutez cette ligne :
var video = document.getElementById('SourceVideo');
Pour commencer le traitement de la vidéo, nous devons attendre que la vidéo soit prête à être lue.
video.addEventListener('canplay', function () { // Définit le canevas avec la même largeur et hauteur que la vidéo canvas.width = video.videoWidth; canvas.height = video.videoHeight; // Lire la vidéo video.play( ); // commence à dessiner les cadres drawFrame(video);});
La lecture d'événement est jouée pendant au moins quelques images lorsqu'il y a suffisamment de données pour lire le média.
Nous ne pouvons voir aucune vidéo affichée sur la toile car nous affichons uniquement la première image. Nous devons exécuter drawFrame toutes les n millisecondes pour suivre la fréquence d'images de la vidéo.
Dans drawFrame, nous appelons à nouveau drawFrame toutes les 10 ms.
function drawFrame(video) { context.drawImage(video, 0, 0); setTimeout(function () { drawFrame(video); }, 10);}
Après avoir exécuté drawFrame, nous créons une boucle qui exécute drawFrame toutes les 10 ms - suffisamment de temps pour que la vidéo reste synchronisée dans le canevas.
Ajouter des effets à la vidéoNous pouvons utiliser la même fonction que nous avons créée précédemment pour inverser les couleurs :
function invertColors(data) { for (var i = 0; i < data.length; i+= 4) { data[i] = data[i] ^ 255; // Inverser les données rouges[i+1] = data[i +1] ^ 255 ; // Inverser les données vertes[i+2] = data[i+2] ^ 255 ; // Inverser le bleu }}
et ajoutez ceci à la fonction drawFrame :
function drawFrame(video) { context.drawImage(video, 0, 0); var imageData = context.getImageData(0, 0, canvas.width, canvas.height); 0, 0); setTimeout(function () { drawFrame(video); }, 10);}
Nous pouvons ajouter un bouton et basculer l'effet :
function drawFrame(video) { context.drawImage(video, 0, 0); if (applyEffect) { var imageData = context.getImageData(0, 0, canvas.width, canvas.height); .putImageData(imageData, 0, 0); } setTimeout(function () { drawFrame(video); }, 10);}utiliser l'appareil photo
Nous conserverons le même code que celui que nous avons utilisé pour la vidéo, la seule différence est que nous utiliserons MediaDevices.getUserMedia pour changer le flux vidéo d'un fichier en flux de caméra.
MediaDevices.getUserMedia est une nouvelle API dépréciant l'API précédente MediaDevices.getUserMedia(). Les navigateurs prennent toujours en charge les anciennes versions, et certains navigateurs ne prennent pas en charge les versions plus récentes, et nous devons recourir aux polyfills pour nous assurer que le navigateur prend en charge l'une d'entre elles.
Tout d’abord, supprimez l’attribut src de l’élément vidéo :
<video><code></pre><pre><code>// Définissez la source de la vidéo sur la fonction de flux de la caméra initCamera(stream) { video.src = window.URL.createObjectURL(stream);}if (navigateur .mediaDevices.getUserMedia) { navigator.mediaDevices.getUserMedia({vidéo : vrai, audio : faux}) .then(initCamera) .catch(console.erreur) );}
Démo en direct
EffetTout ce que nous avons couvert jusqu'à présent constitue la base dont nous avons besoin pour créer différents effets pour des vidéos ou des images. Nous pouvons utiliser de nombreux effets différents en convertissant chaque couleur indépendamment.
Niveaux de grisLa conversion des couleurs en niveaux de gris peut être effectuée de différentes manières en utilisant différentes formules/techniques, pour éviter d'approfondir le problème, je vais vous montrer cinq formules basées sur l'outil de désaturation de GIMP et Luma :
Gris = 0,21R + 0,72G + 0,07B // LuminosityGray = (R + V + B) ÷ 3 // Luminosité moyenneGray = 0,299R + 0,587G + 0,114B // standard rec601Gray = 0,2126R + 0,7152G + 0,0722B / /ITU-RBT.709 standardGris = 0,2627R + 0,6780G + 0,0593B // Norme ITU-R BT.2100
Ce que nous voulons trouver à l'aide de ces formules, c'est le niveau de luminosité de chaque couleur de pixel. La valeur va de 0 (noir) à 255 (blanc). Ces valeurs créeront un effet de niveaux de gris (noir et blanc).
Cela signifie que la couleur la plus claire sera la plus proche de 255 et la couleur la plus foncée sera la plus proche de 0.
Démo en direct
deux tonsLa différence entre un effet bicolore et un effet en niveaux de gris réside dans le fait que deux couleurs sont utilisées. En niveaux de gris, vous avez un dégradé du noir au blanc, tandis qu'en bichromie, vous avez un dégradé de n'importe quelle couleur à n'importe quelle autre couleur (du bleu au rose).
En utilisant la valeur d'intensité des niveaux de gris, nous pouvons la remplacer par la valeur du dégradé.
Nous devons créer un dégradé de ColorA à ColorB.
function createGradient(colorA, colorB) { // Valeurs du dégradé de colorA à colorB var gradient = []; // la valeur de couleur maximale est de 255 var maxValue = 255; object var from = getRGBColor(colorA); var to = getRGBColor(colorB); // Crée 256 couleurs de la couleur A à la couleur B pour (var i = 0; i <= maxValue; i++) { // L'intensitéB passera de 0 à 255 // L'intensitéA passera de 255 à 0 // L'intensitéA diminuera l'intensité tandis que l'intensitéB augmentera // Cela signifie que ColorA commencera à être solide et se transformera lentement en ColorB // Si vous le regardez autrement, la transparence de la couleur A augmentera et la transparence de la couleur B diminuera var intensitéB = i; var intensitéA = maxValue - intensitéB; La formule ci-dessous combine les deux couleurs en fonction de leur intensité // (IntensityA * ColorA + IntensityB * ColorB) / maxValue gradient[i] = { r: (intensityA*from.r + IntensityB*to.r) / maxValue, g: ( intensitéA*from.g + intensitéB*to.g) / maxValue, b : (intensitéA*from.b + intensitéB*to.b) / maxValue } ; gradient;}// Fonction d'assistance pour convertir des valeurs hexadécimales à 6 chiffres en un objet de couleur RVB. 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 : valeur de couleur & 255 }}
En bref, on crée un ensemble de valeurs de couleur en partant de la couleur A, en diminuant l'intensité, tout en allant vers la couleur B et en augmentant l'intensité.
De #0096ff à #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},] ;
Représentation de la mise à l'échelle des transitions de couleurs
Ci-dessus, un exemple de dégradé de 10 valeurs de couleurs allant de #0096ff à #ff00f0.
Représentation en niveaux de gris des transitions de couleurs
Maintenant que nous avons une représentation en niveaux de gris de l'image, nous pouvons l'utiliser pour la mapper à une valeur de dégradé bicolore.
Le dégradé bicolore a 256 couleurs tandis que l'échelle de gris a également 256 couleurs allant du noir (0) au blanc (255). Cela signifie qu'une valeur de couleur en échelle de gris sera mappée à un index d'élément de dégradé.
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) { // Récupère la valeur de couleur de chaque canal var redValue = data[i]; data[i+1]; var blueValue = data[i+2]; // Mappage des valeurs de couleur sur l'indice de dégradé // Remplacement de la valeur de couleur en niveaux de gris par une couleur pour le dégradé bicolore data[i] = gradientColors[ redValue] .r; data[i+1] = gradientColors[greenValue].g; data[i+2] = gradientColors[blueValue].b;
Démo en direct
en conclusionCe sujet pourrait être plus approfondi ou expliquer plus d'implications. Le devoir pour vous est de trouver différents algorithmes qui peuvent être appliqués à ces exemples squelettes.
Comprendre la structure des pixels sur la toile vous permettra de créer un nombre illimité d'effets tels que sépia, mélange de couleurs, effets d'écran vert, flashs/glitchs d'image, etc.
Vous pouvez même créer des effets à la volée sans utiliser d'images ou de vidéos
Ce qui précède représente l’intégralité du contenu de cet article. J’espère qu’il sera utile à l’étude de chacun. J’espère également que tout le monde soutiendra le réseau VeVb Wulin.