Avatars aléatoires en PHP

Voir la vidéo

Introduction

Dans ce tutoriel nous allons nous intéresser à la génération d'avatars aléatoires en php (vous avez sûrement déjà vu ces avatars sur des sites tels que Stackoverflow, GitHub, Wordpress... Ces avatars sont appelés identicons).

Les sources sont disponibles ici

Nous allons voir ici comment générer nos avatars à la manière de GitHub

Les avatars de GitHub

Sommaire :

  1. Pré-requis
  2. Fonctionnement de base
  3. Notre première fonction
  4. Ajout de la symétrie verticale
  5. Personnalisation de l'avatar
  6. Ré-écriture en POO (et ajout d'options)
  7. Exemples d'utilisations

Pré-requis

Pour la partie traitement des images nous allons utiliser la librairie la bibliothèque GD. Vous devrez donc l'activer avant de commencer ce tutoriel (elle est en général activée sur les hébergement mutualisés)

Fonctionnement de base :

  • On part d'une chaîne de caractères (pseudo, adresse IP...)
  • On hash cette chaîne de caractère
  • On extrait une couleur depuis ce hash
  • A partir de ce hash on détérmine si tel carré est coloré ou non
  • On dessine l'image à partir de ces valeurs

Notre première fonction :

<?php
function create($string){
    // On créé le hash à partir de la chaîne de caractères
    $hash = md5($string);

    // On récupère une couleur héxadécimale avec les 6 premiers caractères du hash
    $color = substr($hash, 0, 6);

    // On déclare notre image et nos couleurs
    $image = imagecreate(400, 400);
    $color = imagecolorallocate($image, hexdec(substr($color,0,2)), hexdec(substr($color,2,2)), hexdec(substr($color,4,2)));
    $bg = imagecolorallocate($image, 255, 255, 255);

    // Pour chaque colonne :
        // Pour chaque ligne :
    for($x = 0; $x < 5; $x++){
        for($y= 0; $y < 5; $y++){
            // On récupère le caractère correspondant dans le hash (par exemple 1ière colonne 2ième ligne correspond au 2ième caractère)
            // On décode sa valeur héxadécimale
            // Si le nombre est pair $pixel vaut true sinon $pixel vaut false
            $pixel = hexdec($hash[($x * 5) + $y]) % 2 == 0;

            // Par défaut le bloc est de même couleur que l'arrière-plan
            $pixelColor = $bg;
            // Mais si $pixel vaut true alors on "allume" ce bloc en lui donnant la couleur de $color
            if($pixel){
                $pixelColor = $color;
            }
            // On place chaque bloc de l'image
            imagefilledrectangle($image, $x * 80, $y * 80, ($x + 1) * 80, ($y + 1) * 80, $pixelColor);
        }
    }

    // On affiche l'image
    header('Content-type: image/png');
    imagepng($image);
}
  • On utilise la fonction md5() car elle renvoie un hash sous forme héxadécimale, ce qui est pratique pour récupérer une couleur par la suite.
  • Lorsque l'on définit notre couleur (ligne 12) on doit faire des manipulations puisque les 3 derniers paramètres de la fonction imagecolorallocate() sont la couleur sous forme RGB et non héxadécimale (comme c'est le cas dans notre variable $color) : on décode tout simplement notre couleur héxadécimale tout les deux caratères pour obtenir nos teintes de rouge, de vert et de bleu (la fonction hexdec() permet de décoder une chaîne de caractères héxadécimale, c'est à dire qu'elle la remet sous base de 10).
  • les boucles parcourent chaque carré de notre avatar et récupèrent le caractère du hash se situant à la position de la boucle : si ce caractère - une fois décodé grâce à hexdec() - est pair, alors le carré correspondant à la position de notre boucle est coloré, sinon il est transparent.

Petite précision de @mzkd : si vous voulez un fond transparent, vous pouvez ajouter la ligne suivante après la décraration de $bg :

imagecolortransparent($image, $bg);

Voici le résultat de create('grafikart') :

Avatar pour grafikart

Ajout de la symétrie verticale :

Notre fonction permet déjà de générer un avatar unique à partir d'une chaîne de caractères mais n'est pas symétrique. Pour ajouter cette symétrie on doit revenir sur la valeur de $pixel comme ceci :

// On ne génère que les trois premières colonnes
if($x < 3){
    // Génération normale
    $pixel = hexdec($hash[($x * 5) + $y]) % 2 == 0;
}else{
    // On récupère le caractère ayant servi à générer le carré de la colonne symétrique à celle où l'on se trouve
    $pixel = hexdec($hash[((4 - $x) * 5) + $y]) % 2 == 0;
}

Explications :

Il y a deux cas possible :

  • la boucle n'as pas généré les trois premières colonnes : on continue à prendre de nouveaux caractères de $hash afin de générer de nouveaux carrés.
  • la boucle a généré les trois premières colonnes : on récupère les caractères ayant servis pour générer les anciens carrés.

Vous vous demandez sûrement d'où vient le 4 - x : c'est cette opération qui permet par exemple, lorsque x = 3 (c'est-à-dire à la 4ième colonne), de pointer sur la 2ième colonne (4 - 3 = 1), qui est bien la colonne sur laquelle on doit se baser pour construire une nouvelle colonne. En fait, pour ce faire, on doit retirer le maximum que peut atteindre x (soit 4) et retirer le numéro de la colonne dans laquelle on est. Ainsi on n'obtient non pas le numéro de la colonne dans laquelle on se trouve depuis la gauche mais depuis la droite, ce qui correspond à la colonne symétrique à celle que l'on parcout (et qui a déjà été générée).

Voici le résultat de create('grafikart') avec une symétrie verticale:

Avatar symétrique

Personnalisation de l'avatar :

On va pousser la génération plus loin en ajoutant des options :

  • On pourra choisir le nombre de carrés par colonnes / lignes
  • On pourra choisir la taille de l'image

Le nouveau code :

<?php
function create($string, $blocks = 5, $size = 400){
    // Contient le nombre de colonnes à générer
    $togenerate = ceil($blocks / 2);

    // On créé le hash à partir de la chaîne de caractères
    $hash = md5($string);
    // On détérmine de combien de caractères on va avoir besoin pour générer notre avatar
    $hashsize = $blocks * $togenerate;
    // On rajoute des caractères à $hash si c'est nécessaire
    $hash = str_pad($hash, $hashsize, $hash);

    // On récupère une couleur héxadécimale avec les 6 premiers caractères du hash
    $color = substr($hash, 0, 6);
    // On détermine le côté d'un bloc (en pixels)
    $blocksize = $size / $blocks;

    // On déclare notre image et nos couleurs
    $image = imagecreate($size, $size);
    $color = imagecolorallocate($image, hexdec(substr($color,0,2)), hexdec(substr($color,2,2)), hexdec(substr($color,4,2)));
    $bg = imagecolorallocate($image, 255, 255, 255);

    // Pour chaque colonne :
        // Pour chaque ligne :
    for($x = 0; $x < $blocks; $x++){
        for($y = 0; $y < $blocks; $y++){
            // Si l'on doit encore générer de NOUVELLES colonnes
            if($i < $togenerate){
                // On récupère le caractère correspondant dans le hash (par exemple 1ière colonne 2ième ligne correspond au 2ième caractère)
                // On décode sa valeur héxadécimale
                // Si le nombre est pair $pixel vaut true sinon $pixel vaut false
                $pixel = hexdec($hash[($x * $blocks) + $y]) % 2 == 0;
            }else{
                // On récupère le caractère ayant servi à générer le carré de la colonne symétrique à celle où l'on se trouve
                // $blocks - 1 - $x agit comme le 4 - $x sauf que là on calcule dynamiquement la valeur maximale que $x peut atteindre
                $pixel = hexdec($hash[(($blocks - 1 - $x) * $blocks) + $y]) % 2 == 0;
            }

            // Par défaut le bloc est de même couleur que l'arrière-plan
            $pixelColor = $bg;
            // Mais si $pixel vaut true alors on "allume" ce bloc en lui donnant la couleur de $color
            if($pixel){
                $pixelColor = $color;
            }
            // On place chaque bloc de l'image
            imagefilledrectangle($image, $x * $blocksize, $y * $blocksize, ($x + 1) * $blocksize, ($y + 1) * $blocksize, $pixelColor);
        }
    }

    // On affiche l'image
    header('Content-type: image/png');
    imagepng($image);

}

Le code n'a pas subit d'importants changement, on gère juste tout dynamiquement avec des variables. Les commentaires vous explique chaque étape si jamais il vous manque une explication. ;)

Le code en procédural est fini, dans la deuxième partie nous ré-écrirons le code en POO mais sachez qu'une dernière partie vous montre comment vous servir du code écrit dans ce tuto

Ré-écriture en POO (et ajouts d'options)

Le plus dur est derrière nous, cette partie est juste faite pour ajouter un petit plus, si vous voulez.
Avec le code en POO :

  • Les appels pour créer un avatars seront plus propres et mieux organisés (une instance par avatar)
  • Nous rajouterons des options d'export : ainsi, en plus de pouvoir afficher notre avatar, nous pourrons le sauvegarder dans un fichier et l'exporter en base64

Sans plus tarder, le code :

    class Avatar{

        private $image;

        // Même fonctionnement que create() sauf au moment de l'affichage, se référer aux commentaires faits dans le fichier avatar.php
        public function __construct($string, $blocks = 5, $size = 400){
            $togenerate = ceil($blocks / 2);

            $hash = md5($string);
            $hashsize = $blocks * $togenerate;
            $hash = str_pad($hash, $hashsize, $hash);
            $hash_split = str_split($hash);

            $color = substr($hash, 0, 6);
            $blocksize = $size / $blocks;

            $image = imagecreate($size, $size);
            $color = imagecolorallocate($image, hexdec(substr($color,0,2)), hexdec(substr($color,2,2)), hexdec(substr($color,4,2)));
            $bg = imagecolorallocate($image, 255, 255, 255);

            for($i = 0; $i < $blocks; $i++){
                for($j = 0; $j < $blocks; $j++){
                    if($i < $togenerate){
                        $pixel = hexdec($hash_split[($i * 5) + $j]) % 2 == 0;
                    }else{
                        $pixel = hexdec($hash_split[((4 - $i) * 5) + $j]) % 2 == 0;
                    }

                    $pixelColor = $bg;
                    if($pixel){
                        $pixelColor = $color;
                    }
                    imagefilledrectangle($image, $i * $blocksize, $j * $blocksize, ($i + 1) * $blocksize, ($j + 1) * $blocksize, $pixelColor);
                }
            }

            // Au lieu d'afficher l'image, on la stock dans la variable d'instance $image pour pouvoir la manipuler avec d'autres méthodes
            ob_start();
                imagepng($image);
                $image_data = ob_get_contents();
            ob_end_clean ();
            $this->image = $image_data;
        }

        // Affiche l'image, comme on le faisait dans la fonction create()
        public function display(){
            header('Content-type: image/png');
            echo($this->image);
        }

        // Exporte l'image en base64
        public function base64(){
            return 'data:image/png;base64,' . base64_encode($this->image);
        }

        //Sauvegarde l'image dans un fichier
        public function save($filename){
            if(file_put_contents($filename, $this->image)){
                return $filename;
            }
        }

    }

J'ai mis moins de commentaires sur ce dernier bout de code, j'estime que si vous préférez la solution en POO vous avez un niveau en PHP qui vous permet de comprendre le fonctionnement de cette classe :p

Avec ça je vous donne un code avec des exemples d'utilisations.

Exemples d'utilisations :

<?php
    // Fonction
    require('avatar.php');
    // Classe
    require('AvatarClass.php');

    // Le code qui va suivre es faux car j'affiche plusieurs images
    // Il est juste là pour vous montrer les utilisations possibles

    create('grafikart');
    create('grafikart', 3);
    create('grafikart', 10, 600);

    $grafikart = new Avatar('grafikart');
    echo '<img src="' . $grafikart->base64() . '" />';
    $grafikart->save('thumbs/grafikart.png');
    $grafikart->display();

Vous pouvez jouer avec tous les paramètres bien entendu mais sachez tout de même que la génération d'image est lourde (en termes de performances) : ne laissez pas vos utilisateurs créer un avatar contenant 999 999 blocs, premièrement se serait inutile vu que les motifs de l'avatar se répètent et que la résolution de l'image ne permettrait pas de voir tous les détails mais en plus ça mettrait à genoux votre serveur...

Voilà c'est tout pour ce tutoriel, j'espère qu'il vous aura plu !

Publié
Technologies utilisées
Auteur :
hugopb82
Partager