Ascii video
ASCII VIDEO
Active ta caméra
pour commencer.
Ton naviguateur ne supporte pas la caméra de l'API.
==== Structure Css ====
Les commentaires/explications se situent entre “/*” et “*/”
/* */
body { /* Corp*/
background: #ff; /* fond blanc */
margin: auto; /* marge auto (utiles pour la fixation des titres) */
}
h1 { /* titre niveau 1*/
position: fixed; /* position fixe */
top: 40px; /* à 40 px du haut */
left : 45px; /* à 45 de la gauche */
border: 5px solid #000; /* bordure noir autour de la typo de 5 mm */
padding-left: 5px; /* 5 mm entre la typo et la bordure sur gauche*/
padding-right: 5px; /* 5 mm entre la typo et la bordure sur droite*/
padding-top: 3px; /* 5 mm entre la typo et la bordure sur haut*/
padding-bottom: 3px; /* 5 mm entre la typo et la bordure sur bas*/
color: #000; /* typo couleur noir */
font-size: 25px; /* taille typo */
font-family: Gotham, Helvetica Neue, Helvetica, Arial," sans-serif"; /* Choix typographique via le naviguateur (sir gotham non présente, choisir celle d'apres, etc) */
}
h2{ /* Titre niveau 2 */
position: fixed; /* // */
top: 36px; /* // */
right : 40px; /* // */
font-size: 20px;/* // */
font-family: Gotham, Helvetica Neue, Helvetica, Arial," sans-serif"; /* // */
color: rgba(0,0,0,1.00); /* // */
text-transform: uppercase; /* Texte en majuscule */
}
button { /* Bouton (play) */
text-decoration: none; /* Permet d'enlever le surlignage automatique du texte du bouton */
border: 5px solid rgba(0,0,0,1.00); /* // */
cursor: cell; /* Change la forme du curseur lorsque l'on passe dessus */
overflow: hidden; /* Pas de contenu qui dépasse de la zone */
background: none; /* pas de fond */
font-size: 18px; /* // */
font-weight: bolder; /* typo en gras */
font-family: Gotham, Helvetica Neue, Helvetica, Arial,' sans-serif'cursive; /* // */
padding: 0.5rem 2rem; /* permet de remplacer les notions de padding left, right, ... la première valeur est pour la largeur, la seconde pour la hauteur, rem est une unité variable qui change en fonction de la taille de la typo(valeur native) */
color: rgba(0,0,0,1.00); /* Couleur de la typo, rgba est une autre manière de l'indiquer */
}
button:hover { /* Changement de Style graphique lorsque on passe par dessus le bouton */
box-shadow: 1px 1px 25px 10px rgba(0, 0, 0, 0.4);} /* mettre un dégradé en arrière plan / blanc / opacité 40% */
button:active { /* Changement de Style graphique lorsque on clique sur le bouton */
box-shadow: 1px 1px 80px 25px rgba(0, 0, 0, 0.8); /* mettre un dégradé en arrière plan / blanc / opacité 80% */
}
#ascii { /* Style graphique de la classe ascii */
font-family: 'Courier New', 'Courier', monospace; /* La typographie courier est celle fonctionnant le mieux pour l'image asci car ses bordure et font toute la même largeur*/
font-size: 10px; /* taille de la typographie */
line-height: 10px; /* réglage de l'interlignage pour que le tout soit carré */
color: black; /* // */
letter-spacing: -1.5px; /* Réduction de l'espacement des lettres */
text-align: center; /* alignement des lettres au millieu */
}
h6 { /* Typographie 'active ta caméra' */
font-size: 55px; /* // */
font-weight: 300; /* // */
color: #000; /* Autre méthode de colorisation */
text-align: center; /* // */
position: relative; /* position sous forme d'élement indépendant qui bouge en fonction de la taille de l'écran */
top: 100%; /* // */
text-transform : uppercase; /* // */
font-family: Gotham, Helvetica Neue, Helvetica, Arial," sans-serif"; /* // */
}
#notSupported { /* Même chose que le texte en haut */
display: none;
}
#notSupported h6 { /* Texte ne fonctionne pas passe en rouge */
color: #b41a1a;
margin-bottom: 20px;
}
.title { /* Classe titre est égale à appellé h1 pour un indication supplémentaire */
position: fixed; /* // */
top: 40px; /* // */
left : 45px; /* // */
border: 5px solid #000; /* // */
animation: glitch-middle 3s infinite; /* Création d'une animation appellé 'glitch-middle' / durée 3seconde / qui se répete à l'infini */
}
@keyframes glitch-middle { /* Détail graphique de l'animation */
0%,26%,30%,72%,76%,100% { transform: translate(0em,0em) skew(0deg) ; box-shadow: none } /* Le pourcentage correspond à un moment en % de l'animation (60%de3sec) / n'agit pas */
30%,70% {transform: translate(0em,0em) skew(30deg);} /* déforme, penche le texte de 30 deg */
29%,31%,69%,71% {transform: translate(0em,0em) skew(0deg);} /* retour normale */
28%,74% { box-shadow: /* crée des ombrage à 100 % d'opacité, rose et cyan, qui sont décallés du texte */
-0.2em 0.1em 0 0 cyan,
0.2em -0.1em 0 0 magenta
}
}
==== Scripts JavaScript ====
Les commentaires/explications se situent apres les / /.
**__camera.js__**
// Code de Jacob Seidelin (https://www.nihilogic.dk/labs/jsascii/)
//Modifié par Andrei Gheorghe (https://github.com/idevelop)
//remodifié et expliqué par Lookitsgraphic ( https://lookitsgraphic.com/Projects/Ascii.html/ )
var camera = (function() { //utilise variable option, video, canvas, context, rendertimer
var options;
var video, canvas, context;
var renderTimer;
function initVideoStream() { //fonction commencement de vidéo
video = document.createElement("video"); //créer un element utilisant variable vidéo
video.setAttribute('width', options.width); //attibut largeur
video.setAttribute('height', options.height); //attibut hauteur
video.setAttribute('playsinline', 'true'); // attribut booléen qui indique que la vidéo doit être jouée en incise, c'est-à-dire au sein de la zone de lecture de l'élément.
video.setAttribute('webkit-playsinline', 'true'); //Même que pour playsinline mais pour navigateur safari et mozilla
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; //demande d'utilisation d'un périphérique de l'ordinateur (pour nous la caméra),
window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL; // ne fonctionne que si le site est sécurisé (https) (depuis 2018 par convention de google et autres).
if (navigator.getUserMedia) { //si autorisation
navigator.getUserMedia({
video: true, //vidéo activée
audio: false, //son coupé
}, function(stream) {
options.onSuccess();//renvoi une confirmation vers programme app.js
if (video.mozSrcObject !== undefined) { // renvoi une confirmation programme pour Firefox < 19
video.mozSrcObject = stream;
} else {
video.srcObject = stream; //actif
}
initCanvas();
}, options.onError);
} else {
options.onNotSupported(); //sinon non supporté, renvoi non confirmation vers programme app.js
}
}
function initCanvas() { //lorsque bouton play lancé
canvas = options.targetCanvas || document.createElement("canvas"); //creer une section écran dédier, un canvas
canvas.setAttribute('width', options.width); //attribut largeur
canvas.setAttribute('height', options.height); //attribut hauteur
context = canvas.getContext('2d'); // CanvasRenderingContext2D est utilisée pour dessiner des rectangles (l'écran)
// mirroir video
if (options.mirror) {
context.translate(canvas.width, 0); //largeur du canvas uniquement
context.scale(-1, 1); //-1 = rotation verticale
}
}
function startCapture() { //commencement capture vidéo
video.play(); //la vidéo se lance
renderTimer = setInterval(function() { // definir l'intervale de rendu
try {
context.drawImage(video, 0, 0, video.width, video.height); //dessin des caractères
options.onFrame(canvas); //Dans l'espace défini
} catch (e) {
// TODO //???
}
}, Math.round(1000 / options.fps)); //nombre définir par 1000 divisé par fps
}
function stopCapture() { //Fonction arreter la capture vidéo
pauseCapture(); //Appel fonction vidéo en pause
if (video.mozSrcObject !== undefined) { //pour mozilla <19
video.mozSrcObject = null;
} else {
video.srcObject = null; //pour autre naviguateur
}
}
function pauseCapture() { //Fonction vidéo en pause
if (renderTimer) clearInterval(renderTimer); //stopper sur dernière image du rendu fps
video.pause(); // la vidéo est en pause
}
return {
init: function(captureOptions) { //fonction capture vidéo
var doNothing = function(){}; //récupération image seulement
options = captureOptions || {}; //option de capture image
options.fps = options.fps || 25; //fréquence
options.width = options.width || 640; //taille capture vidéo largeur
options.height = options.height || 480; // taille capture vidéo hauteur
options.mirror = options.mirror || false; // capture vidéo pas en mirroir
options.targetCanvas = options.targetCanvas || null; // TODO: L'élèment est-il actuellement a
**__ascii.js__**
// Code de Jacob Seidelin (https://www.nihilogic.dk/labs/jsascii/)
//Modifié par Andrei Gheorghe (https://github.com/idevelop)
//remodifié et expliqué par Lookitsgraphic ( https://lookitsgraphic.com/Projects/Ascii.html/ )
var ascii = (function() { //créer variable ascii
function asciiFromCanvas(canvas, options) { //utilisant canvas et option
var characters = (" .:-/*0#8%@").split(""); //Variable d'utilisation des caractères, sans aucune séparations. on peut remplacer la chaine de caratères par d'autres. si on travaille sur fond blanc mieux vaut privilégier les caractères allant du plus petit au plus gros, sinon inverse, on peut en ajouter et en supprimer.
var context = canvas.getContext("2d"); //variable créant la zone écran rectangulaire
var canvasWidth = canvas.width; //variable utilisant la largeur caméra
var canvasHeight = canvas.height;// variable utilisant la hauteur caméra
var asciiCharacters = "";
// Variable calculant le contraste / tuto:
// https://www.dfstudios.co.uk/articles/image-processing-algorithms-part-5/
var contrastFactor = (259 * (options.contrast + 255)) / (255 * (259 - options.contrast));
var imageData = context.getImageData(0, 0, canvasWidth, canvasHeight);
for (var y = 0; y < canvasHeight; y += 2) { // récupérer un pixel sur deux sur la hauteur car caractères pas carré
for (var x = 0; x < canvasWidth; x++) {
// obtenir la luminosité de chaque pixel et afficher le caractère correspondant
var offset = (y * canvasWidth + x) * 4;
var color = getColorAtOffset(imageData.data, offset); //variable pour calcul couleur
// Variable accentuant le contraste de l'image pour que chaque caractères se démarquent / tuto :
// https://www.dfstudios.co.uk/articles/image-processing-algorithms-part-5/
var contrastedColor = {
red: bound(Math.floor((color.red - 128) * contrastFactor) + 128, [0, 255]),
green: bound(Math.floor((color.green - 128) * contrastFactor) + 128, [0, 255]),
blue: bound(Math.floor((color.blue - 128) * contrastFactor) + 128, [0, 255]),
alpha: color.alpha
};
// Calculer la luminosité de chaque pixels / question tuto :
// https://stackoverflow.com/questions/596216/formula-to-determine-brightness-of-rgb-color
var brightness = (0.299 * contrastedColor.red + 0.587 * contrastedColor.green + 0.114 * contrastedColor.blue) / 255;
var character = characters[(characters.length - 1) - Math.round(brightness * (characters.length - 1))];
asciiCharacters += character;
}
asciiCharacters += "\n"; //Le \n est utilisé pour trouver une retour ligne de caractères.
}
options.callback(asciiCharacters); //à chaque fin d eligne pixel, retour ligne.
}
function getColorAtOffset(data, offset) { // implémenter la couleur par couche
return {
red: data[offset],
green: data[offset + 1],
blue: data[offset + 2],
alpha: data[offset + 3]
};
}
function bound(value, interval) { //
return Math.max(interval[0], Math.min(interval[1], value));
}
return {
fromCanvas: function(canvas, options) {
options = options || {};
options.contrast = (typeof options.contrast === "undefined" ? 128 : options.contrast);
options.callback = options.callback || doNothing;
return asciiFromCanvas(canvas, options);
}
};
})();
**__app.js__**
// Code de Jacob Seidelin (https://www.nihilogic.dk/labs/jsascii/)
//Modifié par Andrei Gheorghe (https://github.com/idevelop)
//remodifié et expliqué par Lookitsgraphic ( https://lookitsgraphic.com/Projects/Ascii.html/ )
//variable fenetre taille
var w = window.innerWidth
|| document.documentElement.clientWidth //faire appel à la mesure écran de l'ordinateur (largeur)
|| document.body.clientWidth;
var h = window.innerHeight
|| document.documentElement.clientHeight //faire appel à la mesure écran de l'ordinateur (hauteur)
|| document.body.clientHeight;
(function() {
var asciiContainer = document.getElementById("ascii"); //faire appel au code ascii.js
var capturing = false;
camera.init({ //faire démarer la camera
width: (w/7), //utilise variable taille
height: (h/5.2), //utilise variable taille
fps: 25,//nombre image par seconde
mirror: true, //fonction mirroir dans camera, changer par false pour une rotation verticale
onFrame: function(canvas) {
ascii.fromCanvas(canvas, {
// contraste: 128,
callback: function(asciiString) {
asciiContainer.innerHTML = asciiString;
}
});
},
onSuccess: function() { //lorsque l'activation caméra fonctionne
document.getElementById("info").style.display = "none"; //faire disparaitre le message active ta caméra
const button = document.getElementById("button"); //si on appuis sur le bouton
button.style.display = "block";
button.onclick = function() {
if (capturing) {
camera.pause();
button.innerText = 'resume';
} else {
camera.start();
button.innerText = 'pause';
}
capturing = !capturing;
};
},
onError: function(error) {
// TO DO: Envoi un message d'erreur vers le gestionnaire d'erreurs défini
},
onNotSupported: function() { //si la caméra ne fonctionne pas.
document.getElementById("info").style.display = "none"; //remplacer l'élément par ne fonctionne pas
asciiContainer.style.display = "none";
document.getElementById("notSupported").style.display = "block";//faire apparaitre le message à l'écran
}
});
})();
====== Débogage et Infos supplémentaires ======
☛ ATTENTION : ne changez pas l'ordre des fichiers dans le téléchargement sinon vous allez rompre les liens, si vous souhaitez changer les emplacements des sous fichier ( autres que le fichier html), modifiez les liens dans le fichiers "index.html".
☛ Si vous souhaitez héberger ce fichier sur votre propre site internet, il ne fonctionnera que si vous utilisez un protocole HTTPS (sécurisé) et non http, c'est la norme depuis 2018 pour pouvoir accéder à la caméra, le micro, la localisation, ... d'un visiteur web.
☛ Ne fonctionne visiblement pas sur Safari, mais fonctionne sur téléphone.