Classe textImg : édition de texte en image

Cette classe permet de transformer un texte en image PNG avec un fond transparent. Elle utilise la librairie GD version 2. Les caractéristiques principales sont :

Le code complet est disponible en fichier texte.

Voyons ci-dessous des commentaires à propos de cette classe, si vous souhaitez comprendre son fonctionnement ou l'adapter à vos besoins.

Propriétés, constantes et fonction statique

Les propriétés qui caractériseront un objet sont le numéro de ressource,la largeur et hauteur de l'image, le texte écrit en image, la police, la taille, l'angle et la couleur. Les constantes sont spécifiques à l'application qui utilise la classe. La méthode statique sample affiche un petit carré de la couleur sélectionnée :

class textImg {

public $img; //resource
public $width; //width
public $height; //height
public $string;
public $font;
public $size;
public $angle;
public $color; //integer

const PRE = "font/"; //path to the fonts folder
const DFONT = "font/andy.ttf"; //default values
const DSIZE = 20;
const DANGLE = 0;
const WALL = "../images/brickwall.png"; //the background image
const THUMB = "../images/thumb.png"; //temporary thumbnail result
const SAMPLE = "../images/sample.png"; //choosen color sample

static public function sample($tabcol,$file=self::SAMPLE){
  $samp = imagecreatetruecolor(20,20);
  $color = imagecolorallocate($samp,$tabcol[0],$tabcol[1],$tabcol[2]);
  imagefill($samp,0,0,$color);
  imagepng($samp,$file);
}

Le constructeur et le destructeur

Le constructeur gère toute la création d'un objet. Le principe des fonctions de la librairie GD est de travailler sur une ressource image existante, donc la première chose à faire est de créer une image même si on ne connaît pas encore ses dimensions définitives. Ensuite on affecte les valeurs aux autres propriétés.

La valeur par défaut de la couleur est générée à l'intérieur du code constructeur pour la raison évoquée ci-dessus, il faut une ressource existante pour travailler. Enfin la fonction update se charge de l'écriture de la chaîne dans l'image et de la mise à jour des dimensions et de la transparence (voir plus loin).

Les applications graphiques sont gourmandes en ressources mémoire et il est important de libérer la place, une fois qu'elle n'est plus utilisée. Le destructeur se charge de cela.

public function __construct($string=" ",$font=self::DFONT,$size=self::DSIZE,$angle=self::DANGLE,$color="") {
  $this->width = 100;
  $this->height = 100; //fake values, these two variables will be updated later
  $this->img = imagecreatetruecolor($this->width,$this->height);
  $this->chgstring($string);
  $this->chgfont($font);
  $this->chgsize($size);
  $this->chgangle($angle);
  if ($color=="") { //default color
    $grey = imagecolorallocate($this->img,100,100,100);
    $this->color = $grey;
  }
  else $this->chgcolor($color);
}

public function __destruct() {
  imagedestroy($this->img);
}

Affectation de valeurs et transparence de l'image

Voici les méthodes qui affectent une valeur à chaque propriété de l'objet. NB: Il serait possible de mettre les propriétés en private pour une meilleure protection comme seules les méthodes y accèdent. Il faut prévoir dans votre application un contrôle de la valeur des données pour être compatibles avec les résultats attendus. Dans une certaine mesure ce contrôle peut avoir lieu directement dans les méthodes ci-dessous.

Il est possible d'ajouter trés simplement d'autres polices Truetype à utiliser avec cette classe, il suffit de copier le fichier TTF et de donner le chemin d'accés au constructeur, lorsqu'on crée l'objet. Voir l'excellent tutorial La manipulation d'images avec PHP : librairie GD pour plus de détails.

La couleur noire pure est interdite pour le dessin car c'est la couleur choisie pour la transparence d'image. Un peu d'explications ici, avant la version 5.1.1 de PHP, il était impossible d'utiliser une couleur transparente (avec une valeur d'alpha) comme fond d'une image avec la fonction imagefill(). Il semble que cela soit résolu depuis. Cependant pour être compatible avec mon hébergeur qui a une version inférieure de PHP, j'utilise une méthode qui consiste à sélectionner une couleur existante pour la convertir en "transparent pur" (méthode update() dans le paragraphe suivant). Pour faire cela correctement, il faut paramétrer la fonction imagealphablending() à FALSE. L'inconvénient de cette technique est que Internet Explorer ne sait pas gérer ce type de PNG transparent, mais bon ce n'est pas le seul de ses défauts...

public function chgstring($string){
  $this->string = stripslashes($string);
}

public function chgfont($string){ //andy | bickley | chiller | gigi | gothice | harlowsi | jokerman | mistral
  $this->font = realpath(self::PRE.$string.".ttf");
}

public function chgsize($size){
  $this->size = $size;
}

public function chgcolor($tab){
  if ($tab[0]==0 and $tab[1]==0 and $tab[2]==0) { //the pure black (0,0,0) is used for transparency color
    $tab[2]=1;
  }
  $this->color = imagecolorallocate($this->img,$tab[0],$tab[1],$tab[2]);
}

public function chgangle($angle){
  $this->angle = $angle;
}

Redimensionnement optimisé

Le coeur des transformations se trouve dans la méthode update(). Elle permet d'écrire dans l'image le texte entré par l'utilisateur grâce à la fonction imagettftext(). Les dimensions de l'image sont alors recréées dynamiquement en fonction de la longueur du texte et de l'angle d'écriture choisi.

L'astuce est d'écrire dans un premier temps dans l'image de départ uniquement pour récupérer les positions des 4 angles du texte. On utilise ensuite ces positions pour déterminer les dimensions d'une nouvelle image dans laquelle on écrira réellement. La position de départ de l'écriture du texte est également calculée.

On calcule la hauteur et la largeur de la nouvelle image à partir du maximum de distance entre 2 angles opposés. Une marge proportionnelle à la police utilisée est ajoutée autour du rectangle calculé. Il faut aussi choisir une position de départ de l'écriture qui permette d'avoir tout le texte dans la nouvelle image, cette position sera différente selon que l'angle décriture est positif ou négatif.

Dans la plupart des cas, le texte rentrera entièrement dans le cadre défini de cette façon, quelque soit la taille, la police ou l'angle choisi. Pour être sur de pouvoir également intégrer certains cas extrêmes, il suffit d'agrandir la marge $shift, cela ajoutera automatiquement davantage d'espace transparent autour de toutes les images créées.

public function update(){
  $black = imagecolorallocate($this->img,0,0,0);
  $shift = imagefontheight($this->font);
  putenv('GDFONTPATH=' . realpath(self::PRE));
  $pos = imagettftext($this->img,$this->size,$this->angle,0,0,$black,$this->font,$this->string); //the purpose of this line is only to get the 4 positions of the rectangle
  $this->width = max(abs($pos[0]-$pos[4]),abs($pos[2]-$pos[6])) + 2*$shift;
  $this->height = max(abs($pos[1]-$pos[5]),abs($pos[3]-$pos[7])) + 2*$shift;
  $this->img = imagecreatetruecolor($this->width,$this->height);
  imagealphablending($this->img,false);
  imagefill($this->img,0,0,$black);
  imagecolortransparent($this->img,$black);
  if ($this->angle>=0) imagettftext($this->img, $this->size, $this->angle, $shift+max(abs($pos[0]), abs($pos[6])), $this->height-$shift, $this->color, $this->font, $this->string);
  else imagettftext($this->img, $this->size, $this->angle, $shift,abs($pos[1]-$pos[7])+$shift, $this->color, $this->font, $this->string);
  imagepng($this->img,self::THUMB);
}

Fusion d'images

La méthode merge() permet de coller votre texte sur une image de fond prédéfinie. On lui donne en entrée la position sur laquelle il faudra centrer le texte. Par exemple,dans la page grafiti j'utilise la position du click sur le bouton image du formulaire HTML.

Une copie supplémentaire du résultat est faite de temps en temps, pour avoir une certaine traçabilité tout en économisant de l'espace disque.

public function merge($x,$y){ //writes the thumbnail on the background image
  $wall = self::WALL;
  $imgwall = imagecreatefrompng($wall);
  $x = $x-($this->width/2);
  $y = $y-($this->height/2);
  $var = imagecopymerge($imgwall,$this->img,$x,$y,0,0,$this->width,$this->height,80);
  imagepng($imgwall,$wall);
  $numb = rand(0,5);
  if ($numb==4) { //sometimes creates a backup copy image
    $var = date('y-m-d_H-i-s');
    $backup = "./images/temp/brickwall".$var.".png";
    imagepng($imgwall,$backup);
  }
}

}

Conclusion et mise en application

Voila pour les explications. Maintenant abordons les limites ou les possibles améliorations. Les lecteurs perspicaces auront noté qu'on utilise un seul fichier image pour le dessin de l'écriture. Donc deux utilisateurs ne peuvent pas éditer un texte simultanément ou alors il y aura des surprises pour l'un des deux. Il serait donc souhaitable de prévoir une gestion de fichiers temporaires pour permettre une utilisation fiable sur le web. Il pourrait être intéressant aussi de prévoir l'affichage d'un texte avec sauts de lignes.

Vous pouvez tester ce script en action sur la page Grafiti. Pour être complet, voici le code de l'implantation Grafiti en fichier texte. Le principe est d'utiliser une page qui s'envoit à elle même les résultats de formulaires. Selon les variables activées par la dernière requête un affichage différent est fait.