Compare commits

...

6 Commits

16 changed files with 849 additions and 534 deletions

8
.idea/.gitignore generated vendored Normal file
View File

@ -0,0 +1,8 @@
# Default ignored files
/shelf/
/workspace.xml
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml
# Editor-based HTTP Client requests
/httpRequests/

8
.idea/modules.xml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/photo_mem.iml" filepath="$PROJECT_DIR$/.idea/photo_mem.iml" />
</modules>
</component>
</project>

8
.idea/photo_mem.iml generated Normal file
View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/vcs.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

View File

@ -11,7 +11,7 @@ Inutile de garder des photos si vous ne savez pas qui est dessus.
On m'a transmis beaucoup de photos anciennes (1919-1950), et je voulais mémoriser un maximum de noms de participants, pour transmettre tout cela aux descendants . Il y avait urgence car les personnes qui connaissaient ces photos et qui pouvaient me renseigner disparaissaient rapidement. Et donc, plutôt que de tout noter sur des bouts de papier difficiles à gérer, j'ai fait ce petit programme qui m'a été très utile, et en quelques séances passées chez des cousins ou voisins, j'ai pu renseigner une quarantaine de photos. (pas complètement).
Ce programme peut être utilisé aussi pour vos photos de classe, équipe de foot, ou tout autres photos comportant des personnages ... ou animaux !
Utilisation :
## Utilisation :
Mettez vos photos à traiter dans un répertoire, avec le programme Photo_mem_cv et les fichiers opencv.js et haarcascade*.xml
Lancer la page photo_mem.html dans un navigateur, c'est avec firefox que ça marche le mieux. je crois que chromium passe mais IE pas du tout.
Pour commencer, cliquez sur le bouton bleu "choix de la photo", trois cas possibles :
@ -24,16 +24,26 @@ Pour commencer, cliquez sur le bouton bleu "choix de la photo", trois cas possib
Quand vous avez terminé de remplir et de positionner vos étiquettes, n'oubliez pas d'enregistrer le xml. Choisissez le même répertoire que la photo, le nom donné au xml est le même que celui de la photo. Il écrase le fichier en cours.
Attention, il n'y a pas de sauvegarde automatique, et si vous oubliez de sauvegarder, tout est perdu !
Reste à faire :
modifier une personne - en cours ...
saisir / modifier le titre - en cours ...
adapter la taille des étiquettes et du cercle à la résolution de l'image
modifier la couleur des étiquettes et du texte
paramétrer un peu mieux toutes les valeurs de positionnement qui sont en dur.
éviter d'avoir à re-saisir les noms que l'on retrouve sur plusieurs photos - pas facile simplement
reconnaissance faciale pour retrouver les mêmes personnages sur plusieurs photos
barre d'outils ??
...... etc et sans compliquer le programme !!!
## Reste à faire :
* TODO modifier une personne - en cours ...
* TODO saisir / modifier le titre - en cours ...
- autofocus sur le premier champ
- changer l'entrée dans le XML correspondant
* TODO adapter la taille des étiquettes et du cercle à la résolution de l'image
* TODO modifier la couleur des étiquettes et du texte
- avoir un input de type color
* TODO éviter d'avoir à re-saisir les noms que l'on retrouve sur plusieurs photos - pas facile simplement
- utiliser le localstorage pour stocker nos entrées et une library d'autocomplétion à la saisie
* TODO reconnaissance faciale pour retrouver les mêmes personnages sur plusieurs photos
* TODO barre d'outils ??
- à définir
...... etc et sans compliquer le programme !!! (ha ha!)
Désolé si le code n'est pas très aux normes, mais je ne connaissais pas javascript avant ce programme!
## Fait
* ajouter des personnes dans le xml
* télécharger un export des données en xml
* importer un xml
* paramétrer un peu mieux toutes les valeurs de positionnement, qui sont maintenant relatives à la place de l'image, qui n'est plus obligée d'être absolue à 0, 0.

View File

Before

Width:  |  Height:  |  Size: 898 KiB

After

Width:  |  Height:  |  Size: 898 KiB

View File

@ -9,537 +9,114 @@ Licence AGPL v3.0+
ajout fonction modification des personnes
-->
<html>
<head>
<head>
<meta charset="utf-8"/>
<title>Photo mem</title>
<style>
circle {
fill: transparent;
stroke: black;
stroke-width: 2;
}
#bouton_image {background-color:blue;color:white;height: 30px;}
#menu_modif {
display: block;
color: black;
background-color: gainsboro;
cursor: pointer;
border: 2px solid;
border-radius: 6px;
text-align: center;
position: absolute;
width: 80px;
z-index: 10;
}
#ajout {
border:2px solid red;
border-radius: 10px;
top: 100px;
left: 200px;
padding-top: 0.5em;
padding-bottom: 0.5em;
padding-left: 1em;
background-color: Azure;
width: 230px;
}
#ajout input:focus {border: solid 2px green;}
.cotecote {display: inline;}
.visible { visibility:visible; }
.invisible { visibility:hidden; }
.etiq {
background-color:oldlace;
border:1px solid blue;
border-radius: 10px;
position:absolute;
font-family: Arial;
font-size: 13px;
left:-200px;
top: 100px;
}
.tit {
background-color:LightYellow;
border: 2px solid Yellow;
border-radius: 5px;
position:absolute;
left:-200px;
top: 100px;
}
</style>
</head>
<body>
<input id="fichiers" multiple type="file" class=cotecote style="display:none"/>
<button id="bouton_image" onclick="fichiers.click()" title="Charger une image et le XML associé" >Choix de la photo</button>
<input id="file" type="button" class=cotecote value="Enregistrer xml" onclick="enregistrer_fichier(enreg_xml)"/>
<input id="file" type="button" class=cotecote value="Enregistrer image " onclick="image_finale()"/>
<p id="status" class=cotecote >OpenCV.js chargement en cours...</p>
<link rel="stylesheet" type="text/css" href="styles/bulma.min.css"></link>
<link rel="stylesheet" type="text/css" href="styles/style_photomem.css"></link>
</head>
<body onload="init()">
<header>
<section class="hero">
<div class="hero-body">
<p class="title">
Photo Mem
</p>
<p class="subtitle">
Nommer des personnes sur des photos et les partager
</p>
<div id="actions">
<div style="position: relative; ">
<img src="" id="ima_fond" style="position: absolute; left: 0; top: 0; z-index: 0;">
</div>
<input id="fichiers" multiple type="file" class="cotecote button is-primary" value="assets/zizou.png"/>
<button class="button is-secondary" id="bouton_image" onclick="fichiers.click()"
title="Charger une image et le XML associé">
Choix de la
photo
</button>
<input class="button is-secondary" id="save_file" type="button" class=cotecote value="Enregistrer le xml"
onclick="enregistrer_fichier(enreg_xml)"/>
<input class="button is-secondary" id="file" type="button" class=cotecote value="Enregistrer l'image "
onclick="image_finale()"/>
<svg class="invisible" style="position: absolute" ;>
<circle id="cirsvg" cx="30" cy="30" r="28" />
</svg>
</div>
<div id="ajout" class="invisible" style="position: relative; z-index: 1;">
<form name="ajout_personne" id="ajout_personne">
<center><font color="green">NOUVELLE PERSONNE</font></center>
</div>
</section>
</header>
<main class="section is-medium" id="main">
Nom : <INPUT type="text" name="nom" id="nom" autofocus><BR>
Née : <INPUT type="text" name="nom_jf" id="nom_jeune_fille"><BR>
Prénom : <INPUT type="text" name="prénom" id="prenom"><BR>
Année : <INPUT type="text" name="année" id="annee"><BR>
Note : <INPUT type="text" name="note" id="note"><BR>
Titre photo : <INPUT type="text" name="titre" id="titre"><BR>
<INPUT type="reset" id="enreg" value="Ajouter" onClick=ajouter_personne_xml("",souris_x,souris_y,"","");lecture_xml();>
<INPUT type="reset" value="Annuler" onClick=document.getElementById("ajout").className="invisible";>
</form>
</div>
<div id="menu_modif" class="invisible">
<div id="modifier" onClick="modifier_personne();">modifier </div>
<div id="supprimer" onClick="supprimer_personne();menu.style.visibility='hidden';">supprimer</div>
<!-- cercle indiquant la position où on a cliqué-->
<div id="circle_svg" class="invisible animate__pulse" style="left: 0; top:0;"></div>
<!-- formulaire d'ajout ou de modification d'une personne-->
<div id="ajout" class="invisible" style="position: absolute; z-index: 1;">
<form class="content " name="ajout_personne" id="ajout_personne" onsubmit="envoi_formulaire_ajout">
<h2 class="title">
NOUVELLE PERSONNE
</h2>
<div class="field is-horizontal">
<label for="nom">Nom : </label><input class="input" type="text" name="nom" id="nom" autofocus>
</div>
<div class="field is-horizontal">
<label for="nom_jeune_fille">Née : </label><input class="input" type="text" name="nom_jf"
id="nom_jeune_fille">
</div>
<div class="field is-horizontal">
<label for="prenom">Prénom : </label><input class="input" type="text" name="prénom" id="prenom">
</div>
<div class="field is-horizontal">
<label for="annee">Année : </label><input class="input" type="text" name="année" id="annee">
</div>
<div class="field is-horizontal">
<label for="note">Note : </label><input class="input" type="text" name="note" id="note">
</div>
<div class="field is-horizontal">
<label for="titre">Titre photo : </label><input class="input" type="text" name="titre" id="titre">
</div>
<div class="columns">
<div class="column">
<input class="button is-warning" id="reset" type="reset" value="Annuler"
onClick=document.getElementById("ajout").className="invisible";>
</div>
<div class="column has-text-right">
<input class="button is-primary" id="enreg" type="reset" value="Ajouter"
onClick=envoi_formulaire_ajout()>
</div>
</div>
</form>
</div>
<!-- photo de fond-->
<img src="assets/zizou.png" id="ima_fond"/>
<article class="message is-info">
<div class="message-header">
<p>Info</p>
</div>
<div class="message-body">
Sélectionnez une nouvelle photo pour la marquer. Vous pouvez aussi importer une photo et son fichier xml d'information en même temps. </div>
</article>
<div id="menu_modif" class="invisible">
<div id="modifier" onClick="modifier_personne();">modifier</div>
<div id="supprimer" onClick="supprimer_personne()">supprimer</div>
<div id="annuler" onClick="menu.style.visibility='hidden';">annuler</div>
</div>
<script>
</div>
</main>
<footer>
<div class="container">
var personnes=[],nom=[],nomjf=[],prenom=[],annee=[],note=[],posx=[],posy=[],etx=[],ety=[], can_etiq=[];
var ibac=0, tit={}, titre="", fond=[];
var mode_saisie="true" // mode saisie true, mode modification false
var image = document.getElementById('ima_fond');
var cercle = document.getElementById("cirsvg");
var menu = document.getElementById("menu_modif");
var modif_titre = document.getElementById("nouveau_titre");
var cant = document.createElement("canvas");
document.body.appendChild(cant);
Photo mem - J plisson -
<a href="https://forge.chapril.org/Liness/photo_mem">
sources et documentation</a> - GULL liness
</div>
</footer>
let inputElement = document.getElementById('fichiers');
inputElement.addEventListener('change', (e) => {
lecfic(e.target.files)
}, false);
function lecfic(fichier) {
nb_fichier=fichier.length
var i_xml=1, i_img=0;
if (fichier[0].name.split('.').pop()=="xml") {i_xml=0; i_img=1};
var src=fichier[i_img].name.substring(0,fichier[i_img].name.indexOf("."));
enreg_xml=src+".xml"
enreg_noms=src+"_noms.png"
fichier_xml=fichier[i_xml];
fond=fichier[i_img];
affiche_fond();
if (nb_fichier==1) {creation_data_xml(); lecture_xml(); return} // Un seul fichier sélectionné : l'image à traiter
if (fichier_xml.name.startsWith("haar")) {creation_data_xml(); detection_de_visages();return} // deux fichiers selectionnés : l'image et haarcascade.xml
// deux fichiers selectionnés : l'image et le xml du même nom
var reader = new FileReader();
reader.onload = function(evt) {lecture(evt.target.result);};
reader.readAsText(fichier_xml);
function lecture(text) {
var parser = new DOMParser();
data_xml = parser.parseFromString(text,"text/xml");
lecture_xml();
};
}
// Chargement et affichage de l'image background
function affiche_fond() {
var read_fond = new FileReader();
read_fond.addEventListener("load", function (evt) {
image.src = evt.target.result;
}, false);
read_fond.readAsDataURL(fond);
}
function lecture_xml() {
tit = data_xml.documentElement.getElementsByTagName("titre")[0];
titre=tit.textContent
personnes = data_xml.documentElement.getElementsByTagName("personne");
nb_pers=personnes.length
for (var i = 0; i < nb_pers; i++) {
nom[i] = personnes[i].getElementsByTagName( "nom")[0].textContent;
if (personnes[i].getElementsByTagName( "nom_jeune_fille")[0]) {
nomjf[i] = personnes[i].getElementsByTagName( "nom_jeune_fille")[0].textContent;
}
prenom[i] = personnes[i].getElementsByTagName( "prenom")[0].textContent;
annee[i] = personnes[i].getElementsByTagName( "annee_naiss")[0].textContent;
note[i] = personnes[i].getElementsByTagName( "note")[0].textContent;
posx[i] = parseFloat(personnes[i].getElementsByTagName( "position")[0].attributes['posx'].value);
posy[i] = parseFloat(personnes[i].getElementsByTagName( "position")[0].attributes['posy'].value);
etx[i] = parseFloat(personnes[i].getElementsByTagName( "position")[0].attributes['etx'].value);
ety[i] = parseFloat(personnes[i].getElementsByTagName( "position")[0].attributes['ety'].value);
}
if (titre!=='') {affiche_titre(titre)}
affiche_etiquette() ;
};
function affiche_titre(titre_photo) {
cant.className="tit";
cant.height=23;
var taille=titre_photo.length;
cant.width=taille*12;
tx = parseFloat(tit.attributes['titx'].value);
ty = parseFloat(tit.attributes['tity'].value);
cant.style.left = tx + "px";
cant.style.top = ty + "px";
titre_ctx = cant.getContext("2d");
titre_ctx.font = '21px Arial';
titre_ctx.fillStyle = 'Maroon';
titre_ctx.fillText(titre_photo, 20, 18);
affiche_etiquette() ;
}
function affiche_etiquette() {
// affichage du texte nom/prénom dans des canvas etiquette
rayon=28
for (var i = 0; i < nb_pers; i++) {
var id="can"+i;
if (document.getElementById(id)) continue ;
var canv = document.createElement("canvas");
canv.id=id;
document.body.appendChild(canv);
can_etiq[i] = document.getElementById(canv.id);
can_etiq[i].className="etiq";
can_etiq[i].height=30;
etiq_ctx = can_etiq[i].getContext("2d");
var taille=Math.max(nom[i].length,prenom[i].length,nomjf[i].length+5);
can_etiq[i].width=taille*7+10
if (nomjf[i].replace(/\t/g, '').length>1) {can_etiq[i].height=45};
etiq_ctx.font = '13px Arial';
etiq_ctx.fillStyle = 'chocolate';
can_etiq[i].style.left = etx[i] + "px";
can_etiq[i].style.top = ety[i] + "px";
etiq_ctx.fillText(nom[i], 7, 12);
etiq_ctx.fillText(prenom[i], 7, 25);
if (nomjf[i].length>1) {etiq_ctx.fillText("née : "+nomjf[i], 7, 38)};
};
gestion_souris();
};
function gestion_souris() {
if (titre!=='') { // pour déplacer le titre
cant.addEventListener("mousedown", function(e){this.addEventListener("mousemove", deplace_etiquette);});
cant.addEventListener("mouseup", function(e){this.removeEventListener("mousemove", deplace_etiquette);});
}
for (var i = 0; i < nb_pers; i++) { // pour déplacer les étiquettes nom,prénom ...
can_etiq[i].addEventListener("mousedown", function(e){this.addEventListener("mousemove", deplace_etiquette);});
can_etiq[i].addEventListener("mouseup", function(e){this.removeEventListener("mousemove", deplace_etiquette);});
};
ima_fond.addEventListener('mousedown', afficher_formulaire); // pour afficher le formulaire de saisie personne
ima_fond.addEventListener('mousemove', tracer_cercle); // pour afficher cercle personne
cercle.addEventListener('contextmenu', function (e) { menu_modif(e); e.preventDefault();}, false); // menu modif personne
};
function deplace_etiquette(e){
e.target.style.top = e.pageY-15+"px";
e.target.style.left = e.pageX-30+"px";
};
function menu_modif(){
menu.style.left = parseFloat(cercle.getAttribute("cx"))+10 +"px";
menu.style.top = parseFloat(cercle.getAttribute("cy"))+40 +"px";
menu.style.visibility="visible";
}
function supprimer_personne() {
misajour_xml();
x = data_xml.getElementsByTagName("personne")[ibac];
x.parentNode.removeChild(x);
for (var i = 0; i < nb_pers; i++) {
x = can_etiq[i];
x.parentNode.removeChild(x);
};
cercle.style.visibility="hidden";
lecture_xml();
}
function creation_data_xml() {
var x, i;
var txt = "";
var text = "<photo>" +
"<titre titx='50px' tity='50px'>Titre "+ fond.name + "</titre>" +
"<date> </date>" +
"<lieu> </lieu>" +
"<commentaire> </commentaire>" +
"</photo>";
var parser_face = new DOMParser();
data_xml = parser_face.parseFromString(text,"text/xml");
// documentElement always represents the root node
x = data_xml.documentElement.childNodes;
for (i = 0; i < x.length ;i++) {
txt += x[i].nodeName + ": " + x[i].childNodes[0].nodeValue + "<br>";
}
};
function detection_de_visages() {
// alert pour temporiser
alert("Ok pour continuer")
src = cv.imread(ima_fond);
gray = new cv.Mat();
cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY, 0);
chargement_haarcascade();
}
function chargement_haarcascade() {
var reader_haar = new FileReader();
reader_haar.onload = function(ev) {
function str2ab(text) {
return new TextEncoder().encode(text);
}
faceCascadeFile = fichier_xml.name;
let data=str2ab(reader_haar.result)
cv.FS_createDataFile('/', faceCascadeFile, data, true, false, false);
detect_faces();
};
reader_haar.readAsText(fichier_xml);
}
function detect_faces() {
let faces = new cv.RectVector();
let faceCascade = new cv.CascadeClassifier();
// load pre-trained classifiers
faceCascade.load(faceCascadeFile);
// detect faces
let msize = new cv.Size(0, 0);
faceCascade.detectMultiScale(gray, faces, 1.1, 3, 0, msize, msize);
console.log("Il y a "+ faces.size()+" visage(s)")
for (let i = 0; i < faces.size(); ++i) {
ajouter_personne_xml("nom_"+parseInt(i)+" ",faces.get(i).x,faces.get(i).y,faces.get(i).width,faces.get(i).height)
}
// cv.imshow('ima_fond', src);
src.delete(); gray.delete(); faceCascade.delete();
faces.delete();
lecture_xml();
;}
function ajouter_personne_xml(nom_face,fx,fy,fw,fh) {
var nomform=nom_face
var nomjfform=""
var prenomform=""
var anneeform=""
var noteform=""
var posxx=fx+fw/2
var posyy=fy+fh/2
var etxx=fx+10
var etyy=fy+fh*2
document.getElementById('ajout').className='invisible';
if (!nom_face) {
posxx=fx
posyy=fy
etxx=fx-30
etyy=fy+80
nomform=document.getElementById("nom").value
nomjfform=document.getElementById("nom_jeune_fille").value
prenomform=document.getElementById("prenom").value
anneeform=document.getElementById("annee").value
noteform=document.getElementById("note").value
}
// création d'un nouvel élément <personne> dans data_xml
var personne_ = data_xml.createElement("personne");
var nom_ = data_xml.createElement("nom");
nom_.appendChild(data_xml.createTextNode(nomform));
var nom_jf_ = data_xml.createElement("nom_jeune_fille");
nom_jf_.appendChild(data_xml.createTextNode(nomjfform));
var prenom_ = data_xml.createElement("prenom");
prenom_.appendChild(data_xml.createTextNode(prenomform));
var annee_ = data_xml.createElement("annee_naiss");
annee_.appendChild(data_xml.createTextNode(anneeform));
var note_ = data_xml.createElement("note");
note_.appendChild(data_xml.createTextNode(noteform));
var position_ = data_xml.createElement("position");
position_.setAttribute("posx", posxx);
position_.setAttribute("posy", posyy);
position_.setAttribute("etx", etxx);
position_.setAttribute("ety", etyy);
personne_.appendChild(nom_);
personne_.appendChild(nom_jf_);
personne_.appendChild(prenom_);
personne_.appendChild(annee_);
personne_.appendChild(note_);
personne_.appendChild(position_);
data_xml.getElementsByTagName("photo")[0].appendChild(personne_)
};
function modifier_personne() {
mode_saisie="false";
afficher_formulaire(this);
document.getElementById('nom').setAttribute('value', nom[ibac]);
document.getElementById('nom_jeune_fille').setAttribute('value', nomjf[ibac]);
document.getElementById('prenom').setAttribute('value', prenom[ibac]);
document.getElementById('annee').setAttribute('value', annee[ibac]);
document.getElementById('note').setAttribute('value', note[ibac]);
document.getElementById('titre').setAttribute('value', titre);
menu.style.visibility='hidden';
document.getElementById("enreg").style.visibility="hidden";
alert("Fonction en cours de développement")
};
function position_souris(canvas, evt) {
var rect = canvas.getBoundingClientRect();
return {
x: evt.clientX - rect.left,
y: evt.clientY - rect.top
};
};
function tracer_cercle(e) {
document.getElementsByTagName("svg")[0].setAttribute('height', image.naturalHeight);
document.getElementsByTagName("svg")[0].setAttribute('width',image.naturalWidth);
var pos_x=position_souris(ima_fond, e).x;
var pos_y=position_souris(ima_fond, e).y;
for (var i = 0; i < nb_pers; i++) {
var dx = posx[i] - pos_x;
var dy = posy[i] - pos_y;
var dist2 = dx * dx + dy * dy;
var rayon2 = rayon * rayon;
if (dist2 < rayon2) {
ibac=i
can_etiq[i].style.background = "palegreen"
cercle.setAttribute("cx",posx[i]);
cercle.setAttribute("cy",posy[i]);
cercle.setAttribute("r",rayon);
cercle.style.visibility="visible";
// grossir ou diminuer la taille du cercle
// cercle.addEventListener("wheel", function(e) {
// rayon=rayon + (e.deltaY>0 ? -1 : +1);
// cercle.setAttribute("r",rayon );
// } , false);
return
}
};
cercle.style.visibility="hidden";
if (nb_pers!=0) {can_etiq[ibac].style.backgroundColor = "oldlace";}
};
function afficher_formulaire(e) {
var formul=document.getElementById('ajout')
souris_x = position_souris(ima_fond, e).x ;
souris_y = position_souris(ima_fond, e).y;
if (souris_y-document.getElementById('ajout').height<0) {sourisy=50};
formul.style.top=souris_y+40+"px";
formul.style.left=souris_x-80+"px";
setTimeout(function () { document.getElementById("nom").focus(); },10);
formul.className='visible';
// document.getElementById('ajout_personne').reset(); // ne fonctioone pas !
if (mode_saisie) {
document.getElementById('nom').setAttribute('value', "");
document.getElementById('nom_jeune_fille').setAttribute('value', "");
document.getElementById('prenom').setAttribute('value', "");
document.getElementById('annee').setAttribute('value', "");
document.getElementById('note').setAttribute('value', "");
}
mode_saisie="true";
};
function misajour_xml(){
tit.setAttribute("titx",cant.style.left);
tit.setAttribute("tity",cant.style.top);
for (var i = 0; i < nb_pers; i++) {
position=data_xml.getElementsByTagName( "position")[i];
position.setAttribute("etx",can_etiq[i].style.left);
position.setAttribute("ety",can_etiq[i].style.top);
};
lecture_xml();
} ;
function enregistrer_fichier(fichier) {
misajour_xml()
var texte_xml=(new XMLSerializer()).serializeToString(data_xml)
texte_xml=formatXml(texte_xml)
var blob = new Blob([texte_xml], {type: "text/plain;charset=utf-8"});
if (navigator.msSaveBlob) {
navigator.msSaveBlob(blob, fichier);
} else {
var a = document.createElement('a');
if (a.download === '');
a.setAttribute('download', fichier);
a.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(texte_xml));
a.style.display = 'none';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
}
function formatXml(xml, tab) { // tab = optional indent value, default is tab (\t)
var formatted = '', indent= '';
tab = tab || '\t';
xml.split(/>\s*</).forEach(function(node) {
if (node.match( /^\/\w/ )) indent = indent.substring(tab.length); // decrease indent by one 'tab'
formatted += indent + '<' + node + '>\r\n';
if (node.match( /^<?\w[^>]*[^\/]$/ )) indent += tab; // increase indent
});
return formatted.substring(1, formatted.length-3);
}
function image_finale() {
misajour_xml()
var decalx=5;
var decaly=29;
var can_imf = document.createElement("canvas");
document.body.appendChild(can_imf);
imf_ctx = can_imf.getContext("2d");
can_imf.width=ima_fond.width;
can_imf.height=ima_fond.height;
imf_ctx.drawImage(ima_fond,0,0)
if (titre!=='') {
var taille=titre.length;
hauteur=25
roundedRect(imf_ctx,tx-decalx,ty-decaly,taille*11,hauteur,5,'lightyellow','yellow');
imf_ctx.drawImage(cant, tx-decalx, ty-decaly)
};
for (var i = 0; i < nb_pers; i++) {
var taille=Math.max(nom[i].length,prenom[i].length,nomjf[i].length+5);
hauteur=30
if (nomjf[i].replace(/\t/g, '').length>1) {hauteur=45};
// if (!nomjf[i].charCodeAt(1)==9) {hauteur=45};
roundedRect(imf_ctx,etx[i]-decalx,ety[i]-decaly,taille*7+10,hauteur,15,'oldlace','blue');
imf_ctx.drawImage(can_etiq[i],etx[i]-decalx,ety[i]-decaly)
}
var a = document.createElement('a');
if (a.download === '');
a.setAttribute('download', enreg_noms);
a.setAttribute('href', can_imf.toDataURL("image/png").replace("image/png", "image/octet-stream"));
a.style.display = 'none';
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
document.body.removeChild(can_imf);
function roundedRect(ctx, x, y, width, height, radius ,couleur_fond ,couleur_trait) {
ctx.beginPath();
ctx.strokeStyle = couleur_trait;
ctx.moveTo(x, y + radius);
ctx.lineTo(x, y + height - radius);
ctx.quadraticCurveTo(x, y + height, x + radius, y + height);
ctx.lineTo(x + width - radius, y + height);
ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius);
ctx.lineTo(x + width, y + radius);
ctx.quadraticCurveTo(x + width, y, x + width - radius, y);
ctx.lineTo(x + radius, y);
ctx.quadraticCurveTo(x, y, x, y + radius);
ctx.closePath();
ctx.fillStyle = couleur_fond;
ctx.fill();
ctx.stroke();
}
}
</script>
<script>
function onOpenCvReady() {
document.getElementById('status').innerHTML = 'OpenCV.js est chargé !';
}
</script>
<script src="opencv.js" onload="onOpenCvReady();" type="text/javascript"></script>
</body>
<script type="application/javascript" src="scripts/main.js"></script>
</body>
</html>

View File

@ -0,0 +1,53 @@
/******************************************
* détection de visages avec openCV
******************************************/
function onOpenCvReady() {
console.info('OpenCV.js est chargé !');
document.getElementById('status').remove()
}
function detection_de_visages() {
src = cv.imread(ima_fond);
gray = new cv.Mat();
cv.cvtColor(src, gray, cv.COLOR_RGBA2GRAY, 0);
chargement_haarcascade();
}
function chargement_haarcascade() {
let reader_haar = new FileReader();
reader_haar.onload = function (ev) {
function str2ab(text) {
return new TextEncoder().encode(text);
}
faceCascadeFile = fichier_xml.name;
let data = str2ab(reader_haar.result)
cv.FS_createDataFile('/', faceCascadeFile, data, true, false, false);
detect_faces();
};
reader_haar.readAsText(fichier_xml);
}
function detect_faces() {
let faces = new cv.RectVector();
let faceCascade = new cv.CascadeClassifier();
// load pre-trained classifiers
faceCascade.load(faceCascadeFile);
// detect faces
let msize = new cv.Size(0, 0);
faceCascade.detectMultiScale(gray, faces, 1.1, 3, 0, msize, msize);
console.log("Il y a " + faces.size() + " visage(s)")
for (let i = 0; i < faces.size(); ++i) {
ajouter_personne_xml("nom_" + parseInt(i) + " ", faces.get(i).x, faces.get(i).y, faces.get(i).width, faces.get(i).height)
}
src.delete();
gray.delete();
faceCascade.delete();
faces.delete();
lecture_xml();
;
}

479
scripts/main.js Normal file
View File

@ -0,0 +1,479 @@
/**
* définition des constantes
*/
let personnes = [], nom = [], nomjf = [], prenom = [], annee = [], note = [], posx = [], posy = [], etx = [], ety = [],
can_etiq = [];
let ibac = 0, tit = {}, titre = "", fond = [];
let mode_saisie = "true" // mode saisie true, mode modification false
let image = document.getElementById('ima_fond');
const cercle = document.getElementById("circle_svg");
let menu = document.getElementById("menu_modif");
let modif_titre = document.getElementById("nouveau_titre");
let canevas_photo = document.createElement("canvas");
document.body.appendChild(canevas_photo);
let nb_pers = 0;
/**
* écoute du changement de l'input fichier pour charger la photo
* @type {HTMLElement}
*/
let inputElement = document.getElementById('fichiers');
inputElement.addEventListener('change', (e) => {
lecture_photo(e.target.files)
}, false);
function init(){
console.log("lancement de l'initialisation");
gestion_souris();
}
function lecture_photo(fichier) {
nb_fichier = fichier.length
let i_xml = 1, i_img = 0;
if (fichier[0].name.split('.').pop() == "xml") {
i_xml = 0;
i_img = 1
}
let src = fichier[i_img].name.substring(0, fichier[i_img].name.indexOf("."));
enreg_xml = src + ".xml"
enreg_noms = src + "_noms.png"
fichier_xml = fichier[i_xml];
fond = fichier[i_img];
affiche_fond();
// Un seul fichier sélectionné : l'image à traiter
if (nb_fichier == 1) {
creation_data_xml();
lecture_xml();
return
}
// deux fichiers selectionnés : l'image et haarcascade.xml
if (fichier_xml.name.startsWith("haar")) {
creation_data_xml();
detection_de_visages();
return
}
// deux fichiers selectionnés : l'image et le xml du même nom
let reader = new FileReader();
reader.onload = function (evt) {
lecture(evt.target.result);
};
reader.readAsText(fichier_xml);
function lecture(text) {
let parser = new DOMParser();
data_xml = parser.parseFromString(text, "text/xml");
lecture_xml();
};
}
/**
* Chargement et affichage de l'image background
*/
function affiche_fond() {
let read_fond = new FileReader();
read_fond.addEventListener("load", function (evt) {
image.src = evt.target.result;
console.log('image', image, image.offsetLeft, image.offsetTop);
}, false);
read_fond.readAsDataURL(fond);
}
function lecture_xml() {
tit = data_xml.documentElement.getElementsByTagName("titre")[0];
titre = tit.textContent
personnes = data_xml.documentElement.getElementsByTagName("personne");
nb_pers = personnes.length
console.log('image', image, image.offsetLeft, image.offsetTop);
for (let i = 0; i < nb_pers; i++) {
nom[i] = personnes[i].getElementsByTagName("nom")[0].textContent;
if (personnes[i].getElementsByTagName("nom_jeune_fille")[0]) {
nomjf[i] = personnes[i].getElementsByTagName("nom_jeune_fille")[0].textContent;
}
prenom[i] = personnes[i].getElementsByTagName("prenom")[0].textContent;
annee[i] = personnes[i].getElementsByTagName("annee_naiss")[0].textContent;
note[i] = personnes[i].getElementsByTagName("note")[0].textContent;
posx[i] = parseFloat(personnes[i].getElementsByTagName("position")[0].attributes['posx'].value + image.offsetLeft);
posy[i] = parseFloat(personnes[i].getElementsByTagName("position")[0].attributes['posy'].value + image.offsetTop);
etx[i] = parseFloat(personnes[i].getElementsByTagName("position")[0].attributes['etx'].value + image.offsetLeft);
ety[i] = parseFloat(personnes[i].getElementsByTagName("position")[0].attributes['ety'].value + image.offsetTop);
}
if (titre !== '') {
affiche_titre(titre)
}
affiche_etiquette();
};
function affiche_titre(titre_photo) {
canevas_photo.className = "titre";
canevas_photo.height = 23;
let taille = titre_photo.length;
canevas_photo.width = taille * 12;
tx = parseFloat(tit.attributes['titx'].value + image.offsetLeft);
ty = parseFloat(tit.attributes['tity'].value + image.offsetTop);
canevas_photo.style.left = tx + "px";
canevas_photo.style.top = ty + "px";
titre_ctx = canevas_photo.getContext("2d");
titre_ctx.fillText(titre_photo, 20, 18);
affiche_etiquette();
}
/**
* affichage du texte nom/prénom dans des canvas etiquette
*/
function affiche_etiquette() {
rayon = 28
for (let i = 0; i < nb_pers; i++) {
let id = "can" + i;
if (document.getElementById(id)) continue;
let canv = document.createElement("canvas");
canv.id = id;
canv.setAttribute('class', 'people_label')
document.querySelector('#main').appendChild(canv);
can_etiq[i] = document.getElementById(canv.id);
can_etiq[i].className = "etiq";
can_etiq[i].height = 30;
etiq_ctx = can_etiq[i].getContext("2d");
let taille = Math.max(nom[i].length, prenom[i].length, nomjf[i].length + 5);
can_etiq[i].width = taille * 7 + 10
if (nomjf[i].replace(/\t/g, '').length > 1) {
can_etiq[i].height = 45
}
;
can_etiq[i].style.left = etx[i] + "px";
can_etiq[i].style.top = ety[i] + "px";
etiq_ctx.fillText(nom[i], 7, 12);
etiq_ctx.fillText(prenom[i], 7, 25);
if (nomjf[i].length > 1) {
etiq_ctx.fillText("née : " + nomjf[i], 7, 38)
}
}
gestion_souris();
}
/**
* mettre en place les évènements de clic sur la photo
*/
function gestion_souris() {
// pour déplacer le titre
if (titre !== '') {
canevas_photo.addEventListener("mousedown", function (e) {
this.addEventListener("mousemove", deplace_etiquette);
});
canevas_photo.addEventListener("mouseup", function (e) {
this.removeEventListener("mousemove", deplace_etiquette);
});
}
// pour déplacer les étiquettes nom,prénom ...
for (let i = 0; i < nb_pers; i++) {
can_etiq[i].addEventListener("mousedown", function (e) {
this.addEventListener("mousemove", deplace_etiquette);
});
can_etiq[i].addEventListener("mouseup", function (e) {
this.removeEventListener("mousemove", deplace_etiquette);
});
}
;
ima_fond.addEventListener('mousedown', afficher_formulaire); // pour afficher le formulaire de saisie personne
ima_fond.addEventListener('mousemove', tracer_cercle); // pour afficher cercle personne
document.getElementById('circle_svg').addEventListener('contextmenu', function (e) {
e.preventDefault();
menu_modif(e);
}, false); // menu modif personne
};
function deplace_etiquette(e) {
e.target.style.top = e.pageY - 15 + "px";
e.target.style.left = e.pageX - 30 + "px";
};
function menu_modif() {
menu.style.left = cercle.offsetLeft + 10 + "px";
menu.style.top = cercle.offsetTop + 40 + "px";
menu.style.visibility = "visible";
}
function supprimer_personne() {
misajour_xml();
x = data_xml.getElementsByTagName("personne")[ibac];
x.parentNode.removeChild(x);
for (let i = 0; i < nb_pers; i++) {
x = can_etiq[i];
x.parentNode.removeChild(x);
}
cercle.style.visibility = "hidden";
lecture_xml();
menu.style.visibility='hidden';
}
function creation_data_xml() {
let x, i;
let txt = "";
let text = "<photo>" +
"<titre titx='50px' tity='50px'>Titre " + fond.name + "</titre>" +
"<date> </date>" +
"<lieu> </lieu>" +
"<commentaire> </commentaire>" +
"</photo>";
let parser_face = new DOMParser();
data_xml = parser_face.parseFromString(text, "text/xml");
// documentElement always represents the root node
x = data_xml.documentElement.childNodes;
for (i = 0; i < x.length; i++) {
txt += x[i].nodeName + ": " + x[i].childNodes[0].nodeValue + "<br>";
}
};
function envoi_formulaire_ajout() {
ajouter_personne_xml("", souris_x, souris_y, "", "");
document.getElementById('circle_svg').setAttribute('class', 'invisible');
lecture_xml();
}
function ajouter_personne_xml(nom_face, fx, fy, fw, fh) {
let nomform = nom_face
let nomjfform = ""
let prenomform = ""
let anneeform = ""
let noteform = ""
let posxx = fx + fw / 2
let posyy = fy + fh / 2
let etxx = fx + 10
let etyy = fy + fh * 2
document.getElementById('ajout').className = 'invisible';
if (!nom_face) {
posxx = fx
posyy = fy
etxx = fx - 30
etyy = fy + 80
nomform = document.getElementById("nom").value
nomjfform = document.getElementById("nom_jeune_fille").value
prenomform = document.getElementById("prenom").value
anneeform = document.getElementById("annee").value
noteform = document.getElementById("note").value
}
// création d'un nouvel élément <personne> dans data_xml
let personne_ = data_xml.createElement("personne");
let nom_ = data_xml.createElement("nom");
nom_.appendChild(data_xml.createTextNode(nomform));
let nom_jf_ = data_xml.createElement("nom_jeune_fille");
nom_jf_.appendChild(data_xml.createTextNode(nomjfform));
let prenom_ = data_xml.createElement("prenom");
prenom_.appendChild(data_xml.createTextNode(prenomform));
let annee_ = data_xml.createElement("annee_naiss");
annee_.appendChild(data_xml.createTextNode(anneeform));
let note_ = data_xml.createElement("note");
note_.appendChild(data_xml.createTextNode(noteform));
let position_ = data_xml.createElement("position");
position_.setAttribute("posx", posxx);
position_.setAttribute("posy", posyy);
position_.setAttribute("etx", etxx);
position_.setAttribute("ety", etyy);
personne_.appendChild(nom_);
personne_.appendChild(nom_jf_);
personne_.appendChild(prenom_);
personne_.appendChild(annee_);
personne_.appendChild(note_);
personne_.appendChild(position_);
data_xml.getElementsByTagName("photo")[0].appendChild(personne_)
};
function modifier_personne() {
mode_saisie = "false";
afficher_formulaire(this);
document.getElementById('nom').setAttribute('value', nom[ibac]);
document.getElementById('nom_jeune_fille').setAttribute('value', nomjf[ibac]);
document.getElementById('prenom').setAttribute('value', prenom[ibac]);
document.getElementById('annee').setAttribute('value', annee[ibac]);
document.getElementById('note').setAttribute('value', note[ibac]);
document.getElementById('titre').setAttribute('value', titre);
menu.style.visibility = 'hidden';
document.getElementById("enreg").style.visibility = "hidden";
alert("Fonction en cours de développement")
};
function position_souris(canvas, evt) {
let rect = canvas.getBoundingClientRect();
return {
x: evt.screenX - rect.left ,
y: evt.screenY - rect.top
};
};
function tracer_cercle(e) {
// document.getElementsByTagName("svg")[0].setAttribute('height', image.naturalHeight);
// document.getElementsByTagName("svg")[0].setAttribute('width', image.naturalWidth);
// let pos_x = position_souris(ima_fond, e).x;
// let pos_y = position_souris(ima_fond, e).y;
// for (let i = 0; i < nb_pers; i++) {
// let dx = posx[i] - pos_x;
// let dy = posy[i] - pos_y;
// let dist2 = dx * dx + dy * dy;
// let rayon2 = rayon * rayon;
//
// if (dist2 < rayon2) {
// ibac = i
// can_etiq[i].style.background = "palegreen"
// cercle.setAttribute("cx", posx[i]);
// cercle.setAttribute("cy", posy[i]);
// cercle.setAttribute("r", rayon);
// cercle.style.visibility = "visible";
// return
// }
// }
// ;
// cercle.style.visibility = "hidden";
// if (nb_pers != 0) {
// can_etiq[ibac].style.backgroundColor = "oldlace";
// }
};
/**
* montre le formulaire l'on clique
* @param e
*/
function afficher_formulaire(e) {
let formulaire_ajout = document.getElementById('ajout')
let highlight_circle = document.getElementById('circle_svg')
souris_x = e.clientX ;
souris_y = e.clientY + document.documentElement.scrollTop;
if (souris_y - document.getElementById('ajout').height < 0) {
sourisy = 50
}
formulaire_ajout.style.left = souris_x + 20 + "px";
formulaire_ajout.style.top = souris_y + 40 + "px";
formulaire_ajout.className = 'visible';
setTimeout(function () {
document.getElementById("nom").focus();
}, 10);
highlight_circle.style.left = souris_x - circle_svg.offsetWidth /2 + 'px';
highlight_circle.style.top = souris_y - circle_svg.offsetHeight/2 + 'px';
highlight_circle.setAttribute('class', 'visible animate__pulse');
if (mode_saisie) {
vider_formulaire();
}
mode_saisie = "true";
};
function vider_formulaire() {
document.getElementById('nom').setAttribute('value', "");
document.getElementById('nom').focus();
document.getElementById('nom_jeune_fille').setAttribute('value', "");
document.getElementById('prenom').setAttribute('value', "");
document.getElementById('annee').setAttribute('value', "");
document.getElementById('note').setAttribute('value', "");
}
function misajour_xml() {
document.getElementById('circle_svg').setAttribute('class', 'invisible');
tit.setAttribute("titx", canevas_photo.style.left);
tit.setAttribute("tity", canevas_photo.style.top);
for (let i = 0; i < nb_pers; i++) {
position = data_xml.getElementsByTagName("position")[i];
position.setAttribute("etx", can_etiq[i].style.left);
position.setAttribute("ety", can_etiq[i].style.top);
}
;
lecture_xml();
};
function enregistrer_fichier(fichier) {
misajour_xml()
let texte_xml = (new XMLSerializer()).serializeToString(data_xml)
texte_xml = formatXml(texte_xml)
let blob = new Blob([texte_xml], {type: "text/plain;charset=utf-8"});
if (navigator.msSaveBlob) {
navigator.msSaveBlob(blob, fichier);
} else {
let a = document.createElement('a');
if (a.download === '') ;
a.setAttribute('download', fichier);
a.setAttribute('href', 'data:text/plain;charset=utf-8,' + encodeURIComponent(texte_xml));
a.style.display = 'none';
document.querySelector('#main').appendChild(a);
a.click();
document.querySelector('#main').removeChild(a);
}
}
function formatXml(xml, tab) { // tab = optional indent value, default is tab (\t)
let formatted = '', indent = '';
tab = tab || '\t';
xml.split(/>\s*</).forEach(function (node) {
if (node.match(/^\/\w/)) indent = indent.substring(tab.length); // decrease indent by one 'tab'
formatted += indent + '<' + node + '>\r\n';
if (node.match(/^<?\w[^>]*[^\/]$/)) indent += tab; // increase indent
});
return formatted.substring(1, formatted.length - 3);
}
function image_finale() {
misajour_xml()
let decalx = 5 + image.offsetLeft;
let decaly = 29 + image.offsetTop;
let can_imf = document.createElement("canvas");
document.querySelector('#main').appendChild(can_imf);
imf_ctx = can_imf.getContext("2d");
can_imf.width = ima_fond.width;
can_imf.height = ima_fond.height;
imf_ctx.drawImage(ima_fond, 0, 0)
if (titre !== '') {
let taille = titre.length;
hauteur = 25
roundedRect(imf_ctx, tx - decalx, ty - decaly, taille * 11, hauteur, 5, 'lightyellow', 'yellow');
imf_ctx.drawImage(canevas_photo, tx - decalx, ty - decaly)
}
for (let i = 0; i < nb_pers; i++) {
let taille = Math.max(nom[i].length, prenom[i].length, nomjf[i].length + 5);
hauteur = 30
if (nomjf[i].replace(/\t/g, '').length > 1) {
hauteur = 45
}
roundedRect(imf_ctx, etx[i] - decalx, ety[i] - decaly, taille * 7 + 10, hauteur, 15, 'oldlace', 'blue');
imf_ctx.drawImage(can_etiq[i], etx[i] - decalx, ety[i] - decaly)
}
let a = document.createElement('a');
if (a.download === '') ;
a.setAttribute('download', enreg_noms);
a.setAttribute('href', can_imf.toDataURL("image/png").replace("image/png", "image/octet-stream"));
a.style.display = 'none';
document.querySelector('#main').appendChild(a);
a.click();
document.querySelector('#main').removeChild(a);
document.querySelector('#main').removeChild(can_imf);
function roundedRect(ctx, x, y, width, height, radius, couleur_fond, couleur_trait) {
ctx.beginPath();
ctx.strokeStyle = couleur_trait;
ctx.moveTo(x, y + radius);
ctx.lineTo(x, y + height - radius);
ctx.quadraticCurveTo(x, y + height, x + radius, y + height);
ctx.lineTo(x + width - radius, y + height);
ctx.quadraticCurveTo(x + width, y + height, x + width, y + height - radius);
ctx.lineTo(x + width, y + radius);
ctx.quadraticCurveTo(x + width, y, x + width - radius, y);
ctx.lineTo(x + radius, y);
ctx.quadraticCurveTo(x, y, x, y + radius);
ctx.closePath();
ctx.fillStyle = couleur_fond;
ctx.fill();
ctx.stroke();
}
}

1
styles/bulma.min.css vendored Normal file

File diff suppressed because one or more lines are too long

165
styles/style_photomem.css Normal file
View File

@ -0,0 +1,165 @@
body {
background: #ccc;
font-family: Arial, SansSerif, "DejaVu Sans";
}
main {
width: 80%;
margin: 0 auto;
}
circle {
fill: transparent;
stroke: black;
stroke-width: 2;
}
#bouton_image {
background-color: #2266cb;
color: white;
}
#menu_modif {
display: block;
color: black;
background-color: gainsboro;
cursor: pointer;
border: 2px solid;
border-radius: 6px;
text-align: center;
position: absolute;
width: 80px;
z-index: 10;
}
#ajout {
border: 2px solid #2266cb;
border-radius: 10px;
top: 100px;
left: 200px;
padding: 1em 2em ;
background-color: Azure;
width: 30em;
z-index: 3;
}
#ajout label {
width: 10em;
display: inline-block;
padding-right: 1em;
}
#ajout input[type=text] {
border: solid 1px cadetblue;
padding: 0.5em;
margin-bottom: 0.25em;
}
#ajout input:focus {
border: solid 2px green;
}
.visible {
visibility: visible;
}
.invisible {
visibility: hidden;
}
.etiq {
background-color: oldlace;
border: 1px solid #2266cb;
border-radius: 10px;
position: absolute;
font-family: Arial;
font-size: 13px;
left: -200px;
top: 100px;
padding: 1em;
}
.titre {
background-color: LightYellow;
border: 2px solid Yellow;
border-radius: 5px;
position: absolute;
left: -200px;
top: 100px;
}
#fichiers {
display: none;
}
#ima_fond {
position: relative;
}
#circle_svg {
z-index: 2;
box-shadow: 0 0 2em #222;
width: 5em;
height: 5em;
display: block;
position: absolute;
background: rgba(255, 255, 255, 0.6);
border-radius: 100%;
-webkit-border-radius: 50%;
-moz-border-radius: 50%;
border-radius: 50%;
-webkit-box-shadow: 0 20px 30px rgba(0, 0, 0, 0.2), inset 0px 10px 30px 5px rgba(255, 255, 255, 1);
-moz-box-shadow: 0 20px 30px rgba(0, 0, 0, 0.2), inset 0px 10px 30px 5px rgba(255, 255, 255, 1);
box-shadow: 0 20px 30px rgba(0, 0, 0, 0.2), inset 0px 10px 30px 5px rgba(255, 255, 255, 1);
}
#circle_svg:after {
background: -moz-radial-gradient(center, ellipse cover, rgba(255,255,255,0.5) 0%, rgba(255,255,255,0) 70%); /* FF3.6+ */
background: -webkit-gradient(radial, center center, 0px, center center, 100%, color-stop(0%,rgba(255,255,255,0.5)), color-stop(70%,rgba(255,255,255,0))); /* Chrome,Safari4+ */
background: -webkit-radial-gradient(center, ellipse cover, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0) 70%); /* Chrome10+,Safari5.1+ */
background: -o-radial-gradient(center, ellipse cover, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0) 70%); /* Opera 12+ */
background: -ms-radial-gradient(center, ellipse cover, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0) 70%); /* IE10+ */
background: radial-gradient(ellipse at center, rgba(255,255,255,0.5) 0%,rgba(255,255,255,0) 70%); /* W3C */
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#80ffffff', endColorstr='#00ffffff',GradientType=1 ); /* IE6-9 fallback on horizontal gradient */
-webkit-border-radius: 50%;
-moz-border-radius: 50%;
border-radius: 50%;
-webkit-box-shadow: inset 0 20px 30px rgba(255, 255, 255, 0.3);
-moz-box-shadow: inset 0 20px 30px rgba(255, 255, 255, 0.3);
box-shadow: inset 0 20px 30px rgba(255, 255, 255, 0.3);
content: "";
width: 4em;
height: 4em;
left: 10px;
position: absolute;
}
@keyframes pulse {
0% {
-webkit-transform:scaleX(1);
transform:scaleX(1);
opacity:0;
}
50% {
opacity:50;
-webkit-transform:scale3d(1.05,1.05,1.05);
transform:scale3d(1.05,1.05,1.05)
}
to {
opacity:100%;
-webkit-transform:scaleX(1);
transform:scaleX(1)
}
}
.animate__pulse {
-webkit-animation-name:pulse;
animation-name:pulse;
-webkit-animation-timing-function:ease-in-out;
animation-timing-function:ease-in-out
}