Filtre vidéo coloré
- Capturer le flux webcam avec GetUserMedia API,
- Le traiter en temps réel avec webgl : lui reproduire une liste d'éffets colorimétriques réalisés avec un logiciel de retouche d'image,
- Le sauvegarder au format webM avec Whammy.js
GetUserMedia API
- Capture l'image de la webcam et le son du micro dans un élément VIDEO d'HTML5
- Validé par le W3C : spécifications
- Pilier de WebRTC (avec RTCPeerConnection et RTCDataChannel)
- Compatibilité : FF 17, Chrome 21, Opera 12. Source : caniuse.com
Cross compatibilité
navigator.getMedia = ( navigator.getUserMedia ||
navigator.webkitGetUserMedia ||
navigator.mozGetUserMedia ||
navigator.msGetUserMedia);
window.URL = window.URL || window.webkitURL;
var get_mediaStream=function(video, func, funcError) {
navigator.getMedia (
{ video: true, audio: false },
// successCallback
function(locMediaStream) {
video.src = window.URL.createObjectURL(locMediaStream);
var timerUserMedia=setInterval(function(){
if(video.readyState==4){
clearInterval(timerUserMedia);
video.width=video.videoWidth,
video.height=video.videoHeight;
video.autoplay=true;
func(video);
}
}, 100);
},
// errorCallback
function(err) {
funcError();
}
);
}
Le Filtre coloré
- Fonction F:(R, V, B) → (R', V', B')
- Doit marcher quelle que soit F
- Stockée sous la forme d'une image contenant toutes les couleurs = palette
- Idée : F1:(R, V, B) → (X,Y) et F2: (X,Y) → (R',V',B')
- La palette représente F2
La palette
- Image de 4096 x 4096 pixels
- Générée avec un script canvas2D
- X = R + 256 * ⌊ B/16 ⌋
Y = V + 256 * (B-16*⌊B/16 ⌋);
- S'inverse en :
R = X - 256 * ⌊ X/256 ⌋
V = Y - 256 * ⌊ Y/256 ⌋
B = 16*⌊ X/256 ⌋ + ⌊ Y/256 ⌋
Application à la palette les effets colorimétriques :
→
→
Reproduction des effets
- Soit un pixel de couleur C=(R, V, B) sur la vidéo
- Coordonnées de C sur la palette :
X = R + 256 * ⌊ B/16 ⌋
Y = V + 256 * (B-16*⌊B/16 ⌋)
- Récupération de la couleur modifiée sur la palette modifiée :
C'=(R', V', B')=palette(X,Y)
- On affiche le pixel avec la couleur C'
WebGL
- Utilisé ici pour de la 2D
- La vidéo est importée sur une texture
- La texture est affichée sur un quad sur tout le viewPort
- La conversion colorimétrique est effectuée dans le shader de fragments (par pixels)
Création du contexte GL
Dans le code HTML :
<canvas id="mon_canvas" width="800" height="600"></canvas>
Après le chargement de la page :
var canvas=document.getElementById("mon_canvas");
var GL =canvas.getContext("experimental-webgl",{antialias: true});
Vidéo to texture
Création :
var texture=GL.createTexture();
GL.bindTexture(GL.TEXTURE_2D, texture);
GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.LINEAR);
GL.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.LINEAR);
Rafraîchissement :
GL.bindTexture(GL.TEXTURE_2D, texture);
GL.texImage2D(GL.TEXTURE_2D, 0, GL.RGB,GL.RGB, GL.UNSIGNED_BYTE, video );
GL.texParameteri( GL.TEXTURE_2D, GL.TEXTURE_WRAP_S, GL.CLAMP_TO_EDGE );
GL.texParameteri( GL.TEXTURE_2D, GL.TEXTURE_WRAP_T, GL.CLAMP_TO_EDGE );
Création de l'écran
var coins= GL.createBuffer ();
GL.bindBuffer(GL.ARRAY_BUFFER, coins);
GL.bufferData(GL.ARRAY_BUFFER, new Float32Array([-1, -1,
1, -1,
1, 1,
-1, 1]),
GL.STATIC_DRAW);
var indices= GL.createBuffer ();
GL.bindBuffer(GL.ELEMENT_ARRAY_BUFFER, indices);
GL.bufferData(GL.ELEMENT_ARRAY_BUFFER, new Uint16Array([1,0,2,3]),GL.STATIC_DRAW);
Rendu
GL.bindBuffer(GL.ARRAY_BUFFER, coins) ;
GL.vertexAttribPointer(position, 2, GL.FLOAT, false,8,0) ;
GL.bindBuffer(GL.ELEMENT_ARRAY_BUFFER, indices);
GL.drawElements(GL.TRIANGLE_STRIP, 4, GL.UNSIGNED_SHORT, 0);
La palette
Stockée en tant que texture.
Envoyée aux shaders en plus de la vidéo (multisampling)
GL.activeTexture(0);
GL.bindTexture(GL.TEXTURE_2D, texturePalette);
GL.activeTexture(1);
GL.bindTexture(GL.TEXTURE_2D, textureVideo);
Le shader de fragments
Appelé pour chaque pixel. La position du pixel est interpolée en fonction de la position retournée par le shader de vertex, gl_Position
.
uniform sampler2D sampler, samplerPalette;
uniform vec2 LH; //dimensions de la video en pixels
void main(void) {
vec2 uv=vec2(gl_FragCoord)/LH;
vec4 vidCol = texture2D(sampler, uv);
float blueBlock=floor(videoCol.b*256.);
float yBlue=floor(blueBlock/16.)/16.;
float xBlue=floor((blueBlock-yBlue*256.)/16.)/16.;
//coordonnées de texture sur la palette :
vec2 XY=vec2(videoCol.r/16.+yBlue, 1.-vidCol.g/16.-xBlue);
gl_FragColor=texture2D(samplerPalette, XY);
}
Enregistrement vidéo
- Whammy.js : encodeur de vidéo WebM en javascript
- Enregistre une vidéo à partir d'un canvas (2D ou webgl)
- Nécessite un rendu à fréquence constante
Whammy.js
Initialisation :
var CAPTURER = new Whammy.Video(16);
Dessin de la scène :
GL.viewport(0.0, 0.0, CV.width, CV.height);
[...] Envoi des textures et rendu de l'écran
GL.flush();
CAPTURER.add(CV);
Fin de l'enregistrement :
var output = CAPTURER.compile();
var href=(window.webkitURL || window.URL).createObjectURL(output);
window.open(href);