J'avais envie de mettre une image à côté de chaque commentaire, mais ne souhaitais pas utiliser un service tiers comme Gravatar. J'ai regardé vite fait quelles pouvaient être les autres solutions existantes mais n'ai pas trouvé mon bonheur parmis, entre autres, VizHash GD de Seb, libravatar, MonsterID et ces dépôts GitHub. Alors voici ma modeste contribution : Avatar.

projet-avatar.png
Note : commentaire choisi au pseudo-hasard ^^

En situation réelle, ça donne ceci et cela.


Comment ça marche

Le code PHP est relativement simple :

class Avatar
{
    private $length = 80;

    public function __construct($text = 'Tiger-222')
    {
        $this->char = strtoupper($text[0]);
        if ( $this->char < 'A' || $this->char > 'Z' ) {
            $this->char = '*';
        }

        $this->color = hash('crc32', $text);  // RGBA
        $this->filename = sprintf('%s_%s_%d.png',
            $this->color, $this->char, $this->length);

        if ( !is_file($this->filename) ) {
            $this->create_thumb();
        }
        $this->render();
    }

    private function color($im, $r, $g, $b, $i) {
        $i *= 3;  // 5 pour 120px
        $r = $r + $this->diffs[0] * $i;
        $g = $g + $this->diffs[1] * $i;
        $b = $b + $this->diffs[2] * $i;
        return imagecolorallocate($im, $r, $g, $b);
    }

    private function create_thumb()
    {
        $s = $this->length / 4;
        list($r_, $g_, $b_, $a_) = str_split($this->color, 2);
        $r = hexdec($r_);
        $g = hexdec($g_);
        $b = hexdec($b_);
        $this->diffs = array(
            (255 - $r) / $this->length,
            (255 - $g) / $this->length,
            (255 - $b) / $this->length
        );
        $i = 16;

        $im = imagecreatetruecolor($this->length, $this->length);
        $color = imagecolorallocate($im, 255, 255, 255);
        imagefilledrectangle($im, 0, 0, $this->length, $this->length, $color);

        // First row
        $shift = $s-1;
        imagefilledrectangle($im, 0*$s+0, 0, 1*$s-1, $shift, $this->color($im, $r, $g, $b, --$i));
        imagefilledrectangle($im, 1*$s+1, 0, 2*$s-1, $shift, $this->color($im, $r, $g, $b, --$i));
        imagefilledrectangle($im, 2*$s+1, 0, 3*$s-1, $shift, $this->color($im, $r, $g, $b, --$i));
        imagefilledrectangle($im, 3*$s+1, 0, 4*$s-1, $shift, $this->color($im, $r, $g, $b, --$i));
        // Second row
        $sshift = $s+1;
        $eshift = 2*$s-1;
        imagefilledrectangle($im, 3*$s+1, $sshift, 4*$s-1, $eshift, $this->color($im, $r, $g, $b, --$i));
        imagefilledrectangle($im, 2*$s+1, $sshift, 3*$s-1, $eshift, $this->color($im, $r, $g, $b, --$i));
        imagefilledrectangle($im, 1*$s+1, $sshift, 2*$s-1, $eshift, $this->color($im, $r, $g, $b, --$i));
        imagefilledrectangle($im, 0*$s+0, $sshift, 1*$s-1, $eshift, $this->color($im, $r, $g, $b, --$i));
        // Third row
        $sshift = 2*$s+1;
        $eshift = 3*$s-1;
        imagefilledrectangle($im, 0*$s+0, $sshift, 1*$s-1, $eshift, $this->color($im, $r, $g, $b, --$i));
        imagefilledrectangle($im, 1*$s+1, $sshift, 2*$s-1, $eshift, $this->color($im, $r, $g, $b, --$i));
        imagefilledrectangle($im, 2*$s+1, $sshift, 3*$s-1, $eshift, $this->color($im, $r, $g, $b, --$i));
        imagefilledrectangle($im, 3*$s+1, $sshift, 4*$s-1, $eshift, $this->color($im, $r, $g, $b, --$i));
        // Last row
        $sshift = 3*$s+1;
        $eshift = 4*$s-1;
        imagefilledrectangle($im, 3*$s+1, $sshift, 4*$s-1, $eshift, $this->color($im, $r, $g, $b, --$i));
        imagefilledrectangle($im, 2*$s+1, $sshift, 3*$s-1, $eshift, $this->color($im, $r, $g, $b, --$i));
        imagefilledrectangle($im, 1*$s+1, $sshift, 2*$s-1, $eshift, $this->color($im, $r, $g, $b, --$i));
        imagefilledrectangle($im, 0*$s+0, $sshift, 1*$s-1, $eshift, $this->color($im, $r, $g, $b, --$i));

        // Paint the letter
        $size = 50;
        $angle = 12;
        $font = './_avatars.ttf';
        $dim = imagettfbbox($size, $angle, $font, $this->char);
        $letter_width = max($dim[0], $dim[2], $dim[4], $dim[6]);
        $letter_height = min($dim[1], $dim[3], $dim[5], $dim[7]);
        $x = ($this->length - $letter_width) / 2; $x += 2;
        $y = ($this->length - $letter_height) / 2;
        // Shadow
        $rgb = 222;
        $shadow = 33;
        if ( ($r + $g + $b) / 3 < 127 ) {
                $rgb = 33;
                $shadow = 222;
        }
        $color = imagecolorallocate($im, $rgb, $rgb, $rgb);
        imagettftext($im, $size, $angle, $x-1, $y-1, $color, $font, $this->char);
        // Letter
        $color = imagecolorallocate($im, $shadow, $shadow, $shadow);
        imagettftext($im, $size, $angle, $x, $y, $color, $font, $this->char);

        imagetruecolortopalette($im, false, 255);
        imagepng($im, $this->filename);
        imagedestroy($im);
    }

    private function render()
    {
        header('Content-Type: image/png');
        readfile($this->filename);
        exit();
    }
}

$text = isset($_GET['t']) ? $_GET['t'] : '*';
new Avatar($text);

Chaque image obtient un dégradé suivant la somme CRC32 du texte passé à la classe Avatar. Comme les choses sont bien faites, une somme CRC32 est un code héxadécimal de longueur 8. Je m'en sers pour déterminer la couleur de base du dégradé :

Somme CRC32 : 81c44142
Rouge = 0x81 = 129
Vert  = 0xc4 = 194
Bleu  = 0x41 = 65
Alpha = 0x42 = 66

Soit la couleur finale : RGB(129, 194, 65) ou #81c441.

J'ignore la valeur alpha puisque le dégradé sera évalué plus tard par un calcul scientifique fort complexe...


Composition de l'image

Une image sera plus compréhensible que des explications fumeuses :

projet-avatar-composition.png

À chaque étape, la couleur est redéfinie suivant 16 palliers.
Par dessus le carré final, s'ajoute la première lettre du texte passé à la classe Avatar, en majuscule. Ainsi qu'une ombre portée. Les couleurs de la lettre et de l'ombre sont inversées suivant la couleur du carré :

projet-avatar-composition-finale.png

La lettre doit être comprise dans l'alphabet [A-Z*], mais vous pouvez modifier le code pour accepter toutes les lettres. J'ai mis cette restriction car je me suis servi de ce service en ligne pour générer le fichier _avatar.ttf ; à vous de faire votre choix ou d'incoporer une police complète.


Intégration

<html>
<head>
    <meta content="utf-8">
    <title>Avatar</title>
    <style>
        img {
            background-color: #fff;
            padding: 1px;
            border: 1px outset #111;
            border-radius: 60px;
        }
    </style>
</head>
<body>
    <img src="avatars/?t=Kevin"/>
    <img src="index2.php"/>
</body>
</html>

Bien entendu, les images générées sont gardées en cache. Aperçu final :

projet-avatar-2.png

Améliorations

Dans l'idéal, j'aurai voulu générer la couleur du carré en utilisant l'adresse IP, mais cette information n'est pas gardée dans BlogoText. Et je n'avais pas vraiment l'envie de bidouiller la base de données pour ajouter ne serait ce que le condensat SHA256 de l'adresse IP du commentateur.
Dans le dépôt GitHub, j'ai ajouté une version qui prend en paramètre l'adresse IP ainsi que la lettre (fichier index2.php). Ça permet d'avoir un pseudonyme différent pour une même adresse IP.

Aussi, si vous trouvez une solution plus courte, une idée d'amélioration ou tout ce que vous voulez, n'hésitez pas à forker et proposer un patch ☺