Décalquer une image à la souris


Utiliser une photo de votre maison pour en faire des plans, utiliser une carte routière pour tracer votre itinéraire, transformer une photo en un dessin stylisé, déterminer les coordonnées d'une série de points d'une image...
Mais comment remplacer ce vieux papier calque qui faisait tout ça ?

Le petit programme php (seulement 24 ko) présenté ici vous permet de recouvrir l'image de votre choix d'un calque transparent (image gif) sur lequelle vous allez pouvoir dessiner à la souris.

En pratique, il suffit de lancer "traits.php3" en cliquant ici.
On arrive sur une page permettant de choisir l'image support et de définir quelques paramètres (affichage de l'image, épaisseur et couleur du trait). Une fois les choix faits, le programme affiche l'image choisie.
Il suffit de cliquer point par point le long du contour à reproduire. Le programme affiche les coordonnées des points sélectionnés et se charge de les relier entre eux (par des segments de lignes droites).
On enregistre de temps en temps (au moins quand on veut "lever la plume") l'ensemble des points dans le fichier calque transparent.

Une fois le tracé complètement terminé, on peut le récupérer sous forme d'image gif et sous forme de fichier javascript contenant toutes les coordonnées.

      


Cliquez ici pour lancer le programme.



Mais comment ça marche ?

Rien de très compliqué, un mélange de html, de javascript et de php, dont voilà les briques principales :

1) Superposer un calque sur votre image (html) :
<div style='position:absolute;left:10%;top:0%;width:600px;z-index:2;margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;'>
<IMG SRC='spacer.gif' WIDTH='100%' onclick='captmouse(event);return false;' STYLE='cursor:crosshair;margin:0px 0px 0px 0px;'>
</div>
<div style='position:absolute;left:10%;top:0%;width:600px;z-index:-2;margin:0px 0px 0px 0px;padding:0px 0px 0px 0px;'>
<IMG SRC='monimage.jpg' WIDTH='100%' STYLE='margin:0px 0px 0px 0px;'>
</div>


Les images sont incluses dans deux <DIV> positionnés en absolu au même endroit, le calque transparent étant placé au dessus (z-index supérieur).
La capture du clic sur le calque transparent est réalisée par appel d'une fonction "captmouse"

2) Créer un calque transparent de la même taille que votre image (php) :

$imgsz=getimagesize($nomimg);
$im = ImageCreate ($imgsz[0],$imgsz[1]);
$couleur_fond = ImageColorAllocate ($im, 200, 200, 200);
imagecolortransparent($im,$couleur_fond);
ImageGif ($im,'sp.gif');
imageDestroy($im);


3) Enregistrer les coordonnées d'un clic de souris sur l'image (php et javascript).
C'est là qu'on définit la fonction captmouse déclenchée par un clic sur l'image :

<?PHP
echo "
function captmouse(evt)
    {
     var offsetX, offsetY;
     if (typeof evt.offsetX != 'undefined')
             {
             offsetX = evt.offsetX;
             offsetY = evt.offsetY;
             }
     else if (typeof evt.layerX != 'undefined')
             {
             offsetX = evt.layerX;
             offsetY = evt.layerY;
             }
     frm.x".(1+count($x)).".value=offsetX*".$ech.";
     frm.y".(1+count($x)).".value=offsetY*".$ech.";
     frm.submit();
     }
";
?>


La fonction est une fonction javascript.
Elle est rendue compatible Internet Explorer et Netscape en testant l'existence de evt.offsetX (evt correspond à l'événement clic sur l'image) et en le remplacant si nécessaire (Netscape) par evt.layerX.
Les coordonnées récupérées par evt.offsetX et Y ou evt.layerX et Y sont entrées dans les champs de saisie x1, y1, x2, y2, x3, y3... d'un formulaire frm affiché à gauche de l'image.
La fonction javascript captmouse est écrite par du code php, ce qui permet d'y inclure deux choses :
- le numéro du point (pour en inscrire les coordonnées dans le bon champ)
- et un variable $ech (échelle) qui permet d'écrire les coordonnées en "pixels image" plutôt qu'en "pixels écran".

4) Calculer le nombre de "pixels image" par "pixel écran" :
Pour que l'allure de la page ne dépende pas trop de la configration du matériel de l'utilisateur, on affiche l'image avec une largeur égale à un certain pourcentage de la taille de l'écran ($largecran).
(on aurait pu utiliser un affichage relatif avec width='80%' par exemple, mais la précision de l'affichage s'avère dans ce cas inférieure à un dimensionnement absolu en pixels).
La première étape consiste à mesurer la largeur de l'écran par javascript (avec un test permettant la compatibilité Internet Explorer et Netscape) :

largecran=(window.innerWidth == undefined)?document.body.offsetWidth:window.innerWidth;
frm.largeur_ecran.value=largecran;
frm.submit();

La largeur de l'écran javascript (en "pixels écran") ainsi obtenue est inscrite dans le champ largeur_ecran du formulaire puis le formulaire est soumis (seulement si la variable php correspondante $HTTP_POST_VARS["largeur_ecran"] n'a pas encore été définie !).
La variable php $largecran est récupérée en php par :

$largecran=$HTTP_POST_VARS["largeur_ecran"];

puis l'échelle $ech est calculée :

$ech=$imgsz[0]/($largimgrelative*$largecran);

On a vu au 2) que $imgsz[0] est la largeur de l'image (en "pixels image")
$largimgrelative*$largecran est la largeur de l'affichage de l'image (en "pixels écran").

Les changement de taille d'écran en cours de route sont gérés par un "body.onresize" :
<BODY onresize="frm.largeur_ecran.value='';frm.submit();">

En cas de redimensionnement de l'écran, on vide le champ correspondant dans le formulaire, et on resoumet de formulaire, ce qui force le recalcul de l'échelle.

5) Afficher les traits (javascript) :
Chaque fois qu'un nouveau point est enregistré dans le formulaire, on affiche par dessus l'image le trait qui le relie au point précédent.
L'affichage est à ce stade un affichage point par point en javascript : chaque trait est composé d'un grand nombre de pixels matérialisés par des balises span de dimension 1px x 1px avec un fond de couleur rouge :
La fonction javascript trace(x1,y1,x2,y2,coulr) relie les points de coordonnées (x1,y1) et (x2,y2) par un trait de couleur coulr :

function trace(x1,y1,x2,y2,coulr)
    {
    //dessine le trait x1 y1 x2 y2 point par point en javascript
    if(x1!=''&&y1!=''&&x2!=''&&y2!='')
        {
        x1=largecran*offsetgauche+1*x1;
        x2=largecran*offsetgauche+1*x2;
        if(Math.abs(x2-x1)>Math.abs(y2-y1))
            {
            x=Math.round(x1)
            while(x!=Math.round(x2))
                {
                y=y1+(y2-y1)*(x-x1)/(x2-x1)
                document.write("<SPAN STYLE='position:absolute;top:"+y+";left:"+x+";background-color:"+coulr+";width:1px;height:1px;font-size:1px;z-index:-1;'></SPAN>")
                x=x+(x2-x1)/Math.abs(x2-x1)
                }
            } // fin if
        else
            {
            y=Math.round(y1)
            while(y!=Math.round(y2))
                {
                x=x1+(x2-x1)*(y-y1)/(y2-y1)
                document.write("<SPAN STYLE='position:absolute;top:"+y+";left:"+x+";background-color:"+coulr+";width:1px;height:1px;font-size:1px;z-index:-1;'></SPAN>")
                y=y+(y2-y1)/Math.abs(y2-y1)
                }
            } //fin de else
        }
    } //fin de fonction trace


Pour tracer tous les traits reliant les points stockés dans le formulaire au 3) :
Les coordonnées des points ont été stockées au paragraphe 3) dans un formulaire (champs x1, y1, x2, y2, x3, y3...).
Après envoi du formulaire, on les récupère en php comme variables post :

$num=1;
while( isset($HTTP_POST_VARS["x".$num]) && isset($HTTP_POST_VARS["y".$num]) && $HTTP_POST_VARS["x".$num]!='' && $HTTP_POST_VARS["y".$num]!='')
    {
    $x[$num]=$HTTP_POST_VARS["x".$num];
    $y[$num]=$HTTP_POST_VARS["y".$num];
    $num++;
    }

Les coordonnées sont maintenant stockées dans deux tableaux $x[] et $y[].
Il suffit d'appeler autant de fois que nécessaire la fonction trace() pour relier tous les points :

echo "<SCRIPT>";
for($num=1;$num"<count($x);$num++)
    {
    echo "trace(".$x[$num]/$ech.",".$y[$num]/$ech.",".$x[$num+1]/$ech.",".$y[$num+1]/$ech.",'red');";
    }
echo "</SCRIPT>";


6) Enregistrer les coordonnées des points dans un fichier texte

Les coordonnées des points ont été stockées au paragraphe 3) dans un formulaire (champs x1, y1, x2, y2, x3, y3...).
Après envoi du formulaire, on les récupère en php comme variables post :

$num=1;
while( isset($HTTP_POST_VARS["x".$num]) && isset($HTTP_POST_VARS["y".$num]) && $HTTP_POST_VARS["x".$num]!='' && $HTTP_POST_VARS["y".$num]!='')
    {
    $x[$num]=$HTTP_POST_VARS["x".$num];
    $y[$num]=$HTTP_POST_VARS["y".$num];
    $num++;
    }

Les coordonnées sont maintenant stockées dans deux tableaux $x[] et $y[].
Il s'agit de les enregistrer dans un fichier texte (qui peut déjà contenir des coordonnées précédemment enregistrées).
Dans cet exemple, les données sont enregistrées au format javascript, dans le fichier points.js.
Ce n'est pas indispensable, cela mais permet d'autres développements.

Le fichier javascript (s'il existe) est ouvert en appending (ajout de données en fin de fichier) pour y ajouter les coordonnées à enregistrer :

//déterminer si le fichier points.js existe
$fex=false;if(file_exists("points.js"))$fex=true;
//ouvrir le fichier en appending (et le créer s'il n'existe pas)
$f=fopen("points.js","a");
//ajouter une première ligne (définition d'un tableau de coordonnées) si le fichier vient d'être créé
$txtjs="xrec=new Array();yrec=new Array();num=0;";
if(!$fex)fputs($f,$txtjs);
//inscrire les points sous forme d'éléments des tableaux de coordonnées
for($num=1;$num<=count($x);$num++)
    {
    $txtjs="num++;xrec[num]=".$x[$num].";yrec[num]=".$y[$num].";";
    fputs($f,$txtjs);
    }
//ajouter un point vide (pour séparer les enregistrements successifs, les levers de plume)
$txtjs="num++;xrec[num]='';yrec[num]='';";
fputs($f,$txtjs);
//fermer le fichier
fclose($f);


7) Inscrire les lignes sur le fichier calque transparent (php)

Il s'agit là de tracer les traits dans le fichier image (spacer.gif) à partir des coordonnées enregistrées dans le fichier texte (points.js).
En fait, on va recréer une nouvelle image transparente qui va venir remplacer celle qui existe déjà :

//on lit le js et place les coordonnées de tous les points dans les tableaux $ximg et $yimg
$txt=file_get_contents("points".$sess.".js");
$txt=str_replace("xrec=new Array();yrec=new Array();num=0","",$txt);
$txt=str_replace("xrec[num]=","",$txt);$txt=str_replace("yrec[num]=","",$txt);
$pts=explode(";num++;",$txt);
foreach($pts as $k=>$v)
    {
    $xy=explode(";",$v);
    $ximg[]=$xy[0];
    $yimg[]=$xy[1];
    }
// on crée une image transparente de la même taille que l'image de départ
$im = ImageCreate ($imgsz[0],$imgsz[1]);
$couleur_fond = ImageColorAllocate ($im, 200, 200, 200);
imagecolortransparent($im,$couleur_fond);
//on trace les traits avec une épaisseur $epaisseur et une couleur rgb ($rcolp, $gcolp, $bcolp) en utilisant imageline
$epaisseur2=max(1,round($epaisseur*$ech)); //épaisseur du trait en pixel image
imagesetthickness($im, $epaisseur2);
$col_poly = imagecolorallocate($im,$rcolp, $gcolp, $bcolp);
for($nn=0;$nn<count($ximg);$nn++)
    {
    if($ximg[$nn]!='' && $yimg[$nn]!='' && $ximg[$nn+1]!='' && $yimg[$nn+1]!='' && $ximg[$nn]!="''" && $yimg[$nn]!="''" && $ximg[$nn+1]!="''" && $yimg[$nn+1]!="''")
        {imageline ( $im , $ximg[$nn] , $yimg[$nn] , $ximg[$nn+1] , $yimg[$nn+1] , $col_poly );}
    }
// on enregistre l'image dans spacer.gif
ImageGif ($im,'spacer.gif');
imageDestroy($im);



Voilà, toutes les briques élémentaires sont en place, ne reste qu'à les assembler :

Cliquez ici pour lancer le programme.