JeuWeb - Crée ton jeu par navigateur
Article [MAP] Générer les tuiles à partir d'une carte image - Version imprimable

+- JeuWeb - Crée ton jeu par navigateur (https://jeuweb.org)
+-- Forum : Discussions, Aide, Ressources... (https://jeuweb.org/forumdisplay.php?fid=38)
+--- Forum : Programmation, infrastructure (https://jeuweb.org/forumdisplay.php?fid=51)
+--- Sujet : Article [MAP] Générer les tuiles à partir d'une carte image (/showthread.php?tid=8194)



[MAP] Générer les tuiles à partir d'une carte image - Xenos - 14-09-2020

[MAP] Générer les tuiles à partir d'une carte image

Script : Créer les cases d'une carte en bdd à partir d'une image

Ce script a une fonction très bête: éviter de passer 15 jours à encoder chacune des cases d'une carte. Plus votre carte est grande, plus ce script peut vous être utile.

L'idée de base est très simple: pour chaque pixel de l'image, on va enregistrer une case de meme coordonnées dans une base de données. Suivant la couleur du pixel, on enregistre un type de terrain.

Nécessaire avant de commencer:
  • Une table "carte" en BDD, vide. Structure requise: x, y (, z) et un ID pour le terrain de la case. voici la mienne :

Code :
CREATE TABLE `carte` (
  `x` smallint(2) NOT NULL default '0',
  `y` smallint(2) NOT NULL default '0',
  `z` tinyint(1) NOT NULL default '0',
  `id_terrain` tinyint(1) unsigned NOT NULL default '0',
  `id_prov` tinyint(1) unsigned NOT NULL default '0',
  `route` tinyint(1) unsigned NOT NULL default '0',
  `eau` tinyint(1) unsigned NOT NULL default '0',
  PRIMARY KEY  (`x`,`y`,`z`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
  • une table contenant une correspondance ID_terrain - nom du terrain - couleur RGB. Exemple :

Code :
--
-- Structure de la table `terrains`
--

CREATE TABLE `terrains` (
  `ID` mediumint(9) NOT NULL auto_increment,
  `Nom` varchar(20) collate utf8_unicode_ci NOT NULL,
  `code_couleur` varchar(6) collate utf8_unicode_ci NOT NULL,
  PRIMARY KEY  (`ID`)
) ENGINE=MyISAM  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci COMMENT='la liste des terrains du continent' AUTO_INCREMENT=16 ;

--
-- Contenu de la table `terrains`
--

INSERT INTO `terrains` (`ID`, `Nom`, `code_couleur`) VALUES
(1, 'Plaine herbeuse', '00ff00'),
(2, 'Plaine cultivée',''),
(3, 'Haute mer', 'ffffff'),
(4, 'Basse mer', '0000ff'),
(5, 'Basse montagne', '800000'),
(6, 'Haute Montagne', 'c0c0c0'),
(7, 'Plaine aride', '808000'),
(8, 'Forêt de feuillus', '008000'),
(9, 'Forêt de conifères', ''),
(10, 'Jungle', '008080'),
(11, 'Ville', '800080'),
(12, 'Village', 'ff00ff'),
(13, 'Volcan', 'ff0000'),
(14, 'Desert', 'ffff00'),
(15, 'Marécages', '00ffff');

* une image représentant votre carte. Utilisez des couleurs de base, enfin, là, perso, j'en utilise 16 max.
ATTENTION: même avec 16 couleurs, votre carte doit être enregistrée en 24 bits minimum. En 16 couleurs, ca ne marchera pas. (je n'ai pas testé les profondeurs de couleurs intermédiaires)

une petite image pour vous donner un exemple :
{Image (attachment) /kalidhia-exemple.png}

Le script

save_image.php

Code :
<?php
/**********************************************************************************************
** @author : Kaiser Christophe
** Script permettant de récupérer une image, et d'encoder une carte correspondante dans une
** base de données.
**
** Basé sur un script utilisant les images comme stockage de données.
** Auteur et origine inconnues.
**
** L'image DOIT avoir une profondeur de couleur en 16 bits minimum
** (en-deça, la correspondance code trouvé / code RGB ne se fait pas)
** personnellement, je travaille avec des images 16 couleurs, que je repasse en 24 bits
***********************************************************************************************/
$lien_img=isset($_POST['lien_img'])?$_POST['lien_img']:NULL; //?
echo '<h1>Sauvegarde d\'une carte en BDD à partir d\'une image</h1>
<p>Pour une grosse image, il est conseillé de sauvegarder les cartes par portion de petite dimensions. Vous pouvez définir une intervalle de lignes à exporter. Si ca réussi, un lien vous permettra d\'exporter la suite automatiquement</p>
<form method="post">
    <fieldset>
        <legend>Paramètres :</legend>
        <label for:"lien_img">Image source :</label>
<select name="lien_img">';

// Configuration du script ici
$liste_extension=Array("jpg","jpeg","gif","png");
$liste_image_exclu=Array(); //images à ne pas afficher dans le menu déroulant
$chemin="carte/"; //chemin relatif vers le dossier contenant les images
$dossier="./".$chemin; // ne pas toucher (récupération des images)
// fin configuration
//initialisation de variables
$last_y=-9999;
$table_bdd="carte"; // a remplacer par le nom de votre table carte
$champs_bdd="`x`, `y`, `z`,  `id_terrain`";  //a remplacer par vos intitulés de champs (mais gardez la structure identique, ou modifiez le script plus loin)
//fin initialisation de variables

//ouverture du dossier des images pour récupérer la liste des images existantes
$dir = @opendir($dossier);
$i=1;
while($file = @readdir($dir))
{
    $partie=explode(".", $file);
    $nb=count($partie)-1;
    if (in_array($partie[$nb], $liste_extension))
    {
        if (!in_array($partie[($nb-1)], $liste_image_exclu))
        {
            $nom=$partie[($nb-1)].".".$partie[$nb];
            $a="";
            if ($lien_img==$nom)
            {
                $a='SELECTED';
            }
            echo '<OPTION name="'.$nom.'" '.$a.'>'.$nom.'</OPTION>';
            $i++;
        }
    }
}
//suite et fin du formulaire de choix
echo '</select><br />
    <p>Lignes à exporter [axe Y]: </p><label for:"min_Y">Min :</label><input Type="text" name="min_Y" value="0" size="4">
    <label for:"max_Y">Max : </label>Max <input Type="text" name="max_Y" value="5" size="4">
    </fieldset>
<input name="Décomposer" value="Décomposer" type="submit"></form><br /><br />';
//fin du formulaire
if(isset($_GET["img"])){$lien_img=$_GET["img"];}
if ($lien_img)
{
    include ("../Admin/config.php"); //inclusion du fichier de configuration connection bdd
    $extension=strstr($lien_img,'.'); //recherche de l'extension de l'image sélectionnée
    //traitement différent suivant l'extension
    switch($extension)
    {
        case ".jpeg" :
        case ".jpg" : $image = imagecreatefromjpeg(getcwd() . "/".$chemin.$lien_img);
            break;
        case ".gif" : $image = imagecreatefromgif(getcwd() . "/".$chemin.$lien_img);
            break;
        case ".png" : $image = imagecreatefrompng(getcwd() . "/".$chemin.$lien_img);
            break;    
    }
    //récupération des dimensions et du nombre de pixels
    $image_x=imagesx($image);
    $image_y=imagesy($image);
    $nb_pixel=($image_x)*($image_y);
    //paramétrage de la portion d'image à décoder
        //Sur Y
    $min_Y=$_POST["min_Y"];
    if(isset($_GET["min_Y"])){$min_Y=$_GET["min_Y"];}
    $max_Y=$_POST["max_Y"];
    if(isset($_GET["max_Y"])){$max_Y=$_GET["max_Y"];}
        //pas de modification sur X... toute la ligne ou rien
        
    
    //création des tableaux pour s'en sortir (contenant les correspondances code RGB => terrain)
    // charger les terrains existants
    $query="SELECT `ID`, `Nom`, `code_couleur` FROM `terrains` ORDER BY `ID` ASC";
    $resultat=mysql_query($query,$base_id) or die ('<p>[terrains] requête : '.$query.'</p>');
    if(mysql_num_rows($resultat)==0) {echo '<p>[terrains] Aucun résultat retourné<br />Requête : '.$query.'</p>';}
    else
    {
        while($t=mysql_fetch_assoc($resultat))
        {
            $col=$t["code_couleur"];
            $terrain[$col]=array('Nom' => $t["Nom"],'ID' => $t["ID"],'RGB' =>$t["code_couleur"]);
        }
    }
    echo '<hr />';
    echo '<h2><font color="green">Taille de l\'Image :</h2>';
    echo "X=".($image_x)."<br />Y=".($image_y);
    echo "</font><br />";
    echo '<a href="save_image.php?min_Y='.($max_Y+1).'&max_Y='.($max_Y+($max_Y-$min_Y)+1).'&img='.$lien_img.'">Encoder la portion suivante</a>';

    /*
    **        EXECUTION à proprement parler
    */

    $requete='INSERT INTO `'.$table_bdd.'` ('.$champs_bdd.') VALUES ';
    $cpt=0; //compteur
    $nbre_maj=50; //nombre d'enregistrements avant d'éxécuter la requete
    $nbre_req=0; //compteur
    $nbre_pix=0; //compteur
    //on parcout l'ensemble des points de l"image.
    for($y=$min_Y;$y<=$max_Y;$y++)
    {
        for($x=0;$x<=$image_x-1;$x++)
        {
            $ok=0;
            //on récupère la couleur du pixel.
            $valeur=imagecolorat($image, $x, $y);
                
            //transformation en couleur RGB
            $r = ($valeur >> 16) & 0xFF;
            $g = ($valeur >> 8) & 0xFF;
            $b = $valeur & 0xFF;
        
            if($r<=9) {$r1='0'.base_convert($r, 10, 16);} else{$r1=base_convert($r, 10, 16);}
            if($g<=9) {$g1='0'.base_convert($g, 10, 16);} else{$g1=base_convert($g, 10, 16);}
            if($b<=9) {$b1='0'.base_convert($b, 10, 16);} else{$b1=base_convert($b, 10, 16);}
        
            $RGB=$r1.$g1.$b1; //echo '#'.$RGB;
        
            //cette couleur correspond-elle à une couleur d'un terrain?
            if(isset($terrain[$RGB]["ID"])){$id_terrain=$terrain[$RGB]["ID"]; $nbre_pix++; $ok=1;}
            else{echo '<p>['.$x.','.$y.'] Couleur inconnue : '.$RGB.'</p>';}

            //petite variable permettant de modifier à la volée les coordonnées d'un pixel (non gérée ici)
            $x_insertion=$x;
            $y_insertion=$y;
        
            //création de la requete d'insertion
            if($ok==1)
            {
                if($cpt==0){$requete.="(".$x_insertion.", ".$y_insertion.", 0, '".$id_terrain."')";}
                else{$requete.=", (".$x_insertion.", ".$y_insertion.", 0, '".$id_terrain."')";}
                $cpt++;
            }
        
            //le seuil de mise a jour est-il arrivé? OUI => exécution requete, et nettoyage de la variable.
            if($cpt==$nbre_maj)
            {
                //on sauvegarde l'ancienne ligne.
                $resultat=mysql_query($requete,$base_id) or die('<p class="erreur">[ligne '.$y_insertion.'] Erreur : '.mysql_error().'<br />Requête : '.$requete.'</p>');
                if(mysql_affected_rows()==0){echo '<p class="erreur">[Erreur ligne '.$y_insertion.'] Requête : '.$requete.'</p>';}
                else{echo '<p>'.mysql_affected_rows().' Cases encodées pour la ligne '.$y_insertion.' </p>';}
                //on réinitialise la requete en ajoutant directement la première case.
                $requete='INSERT INTO `'.$table_bdd.'` ('.$champs_bdd.') VALUES ';
                $nbre_req++;
                $cpt=0;
            }
        
            //mémorisation de la case passée
            $last_y=$y_insertion;    
        }
    }
    //on exécute une dernière fois la requete... si $cpt > 0
    if($cpt>0)
    {
        $resultat=mysql_query($requete,$base_id) or die('<p class="erreur">[ligne '.$y_insertion.'] Erreur : '.mysql_error().'<br />Requête : '.$requete.'</p>');
        if(mysql_affected_rows()==0){echo '<p class="erreur">[Erreur ligne '.$y_insertion.'] Requête : '.$requete.'</p>';}
        else{echo '<p>'.mysql_affected_rows().' Cases encodées pour la ligne '.$y_insertion.' </p>';}
    }
    // un peu de suivi
    echo '<hr />Image : <IMG src="'.$chemin.$lien_img.'" border="1">';

    echo 'Nombre de pixels traités : '.$nbre_pix;
    echo 'Nombre de requetes : '.$nbre_req;    
} // if($lien_image)

remarques: l'image est à placer dans un dossier "carte". Modifier le script sinon.

mon config.php:

Code :
<?php
$myhote="localhost";   // A CHANGER
$myuser="root";        // A CHANGER
$mypass="";            // A CHANGER
$base_id = mysql_connect($myhote, $myuser,$mypass);
if (!isset($base_id))
{
    die("Connexion à la base de données impossible !" . mysql_error());
}
else
{
    $mybase="Nom_de_la_base"; // A CHANGER
    if (! mysql_select_db("$mybase",$base_id))
    {
        die("Impossible de sélectionner la base de données !" . mysql_error());
    }
}
?>

Explications

Alors… chargez: save_image.php
un formulaire apparait, avec deux champs, en gros:
  • menu déroulant: choisissez l'image à sauvegarder

  • lignes à exporter: dépend de la longueur de chaque ligne… perso, je travaille par 5, mais ca doit aller avec bcp plus.


bref: le script prend l'image, lit l'extension (JPEG/PNG/GIF autorisés), et commence à lire l'image, pixel par pixel…

pour chaque pixel, on récupère la couleur RGB… et on compare avec votre table de terrains.
  • Couleur connue ⇒ tuile X/Y aura cet ID de terrain

  • Couleur inconnue ⇒ message d'erreur


Tous les X (variable, initialisé a 50 de base) pixels, requête de sauvegarde.
J'ai fais les 5 premières lignes… content. Je clique sur le lien "Encoder la portion suivante" et c'est reparti pour 5 lignes… jusqu'à sortir de l'image (erreurs affichées si on tente d'aller trop loin)

Et voilà… en quelques clics, votre map est générée.

Améliorations possibles

Y en a plein, mais je mettrai à jour au fur et à mesure des retours sur ce script

Première amélioration (partiellement implémentée): modifier les coordonnées des points…

en gros, le pixel [0,0] pourrait etre décalé et sauvegardé, par exemple, en [25,35] en BDD.

Pour ceci, il suffit de modifier ces lignes:

Code :
//petite variable permettant de modifier à la volée les coordonnées d'un pixel (non gérée ici)
            $x_insertion=$x;
            $y_insertion=$y;

en:

Code :
//petite variable permettant de modifier à la volée les coordonnées d'un pixel (non gérée ici)
            $x_insertion=$x+25;
            $y_insertion=$y+35;