Etant un flemmard qui s'assume, je n'ai jamais eu le courage d'user de Photoshop et The Gimp pour créer les designs des sites web que je codais. Pourtant, il me fallait un moyen de les rendre un minimum attractif... J'ai donc surfé sur un grand nombre de sites à la recherche d'un point commun dans leur graphisme qui les rend moins affreux que les miens.
A vrai dire, si on écarte tout ce qui est de conception purement humaine (logos, bannières, icônes...), il ne reste principalement qu'une chose : les dégradés, en particulier les dégradés verticaux. En effet, un simple dégradé peut donner au visiteur l'illusion d'un relief sur la page et, comme par magie, la rendre beaucoup moins austère.
Or, avec GD, il est largement possible de générer des dégradés avec de très simples formules mathématiques ! Il ne me restait donc plus qu'à créer un script qui prenne en argument les codes couleurs des 2 extrémités, la direction ainsi que la taille du dégradé, et j'aurais enfin des images générées à la volée pour faire un design simple et efficace :-)
A propos des codes couleurs, si vous ne maîtrisez pas encore la syntaxe hexadécimale, cliquez ici pour dérouler le mini-tutoriel associé.
Voici donc le code du générateur en question, à enregistrer par exemple sous le nom gd.php :
<?php
define('HORIZONTAL', 0);
define('VERTICAL', 1);
class Color {
private $red, $green, $blue;
private $alpha = 0;
public function __construct ($str) {
if (func_num_args() == 4) {
$this->red = intval(func_get_arg(0));
$this->green = intval(func_get_arg(1));
$this->blue = intval(func_get_arg(2));
$this->alpha = intval(func_get_arg(3));
}
else {
if (strlen($str) == 3)
$str = $str[0].$str[0].$str[1].$str[1].$str[2].$str[2];
elseif (strlen($str) == 4)
$str = $str[0].$str[0].$str[1].$str[1].$str[2].$str[2].$str[3].$str[3];
$this->red = $this->getColor(substr($str, 0, 2));
$this->green = $this->getColor(substr($str, 2, 2));
$this->blue = $this->getColor(substr($str, 4, 2));
$this->alpha = floor($this->getColor(substr($str, 6, 2)) / 2);
}
}
private function getColor ($str) {
return base_convert($str, 16, 10);
}
public function allocate ($img) {
return imagecolorallocatealpha($img, $this->red, $this->green, $this->blue, $this->alpha);
}
static public function interpolate (Color $start, Color $stop, $percent) {
return new Color(
round($percent * $start->red + (1 - $percent) * $stop->red),
round($percent * $start->green + (1 - $percent) * $stop->green),
round($percent * $start->blue + (1 - $percent) * $stop->blue),
round($percent * $start->alpha + (1 - $percent) * $stop->alpha)
);
}
}
class Image {
private $img;
public function __construct ($x, $y, $background = null) {
$this->img = imagecreatetruecolor($x, $y);
imagealphablending($this->img, false);
imagesavealpha($this->img, true);
if ($background)
$this->setPolygon(array(
0, 0,
$x, 0,
$x, $y,
0, $y
), $background);
}
public function setPixel($x, $y, Color $color) {
$c = $color->allocate($this->img);
imagesetpixel($this->img, $x, $y, $c);
}
public function setPolygon($points, Color $color) {
$c = $color->allocate($this->img);
$num_points = count($points) / 2;
imagefilledpolygon($this->img, $points, $num_points, $c);
}
public function renderPNG () {
header('Content-type: image/png');
imagepng($this->img);
}
}
switch (@$_GET['type']) {
case 'gradient':
$size = intval(@$_GET['size']);
$direction = (@$_GET['direction'] == 'horizontal') ? HORIZONTAL : VERTICAL;
$start = (string)@$_GET['start'];
$stop = (string)@$_GET['stop'];
$pow = floatval(@$_GET['pow']);
$start = new Color($start);
$stop = new Color($stop);
switch ($direction) {
case HORIZONTAL:
$image = new Image($size, 1);
for ($i = 0 ; $i < $size ; $i++) {
$r = $i / max(1, $size - 1);
$image->setPixel($i, 0, Color::interpolate($start, $stop, pow($r, $pow)));
}
break;
case VERTICAL:
$image = new Image(1, $size);
for ($i = 0 ; $i < $size ; $i++) {
$r = $i / max(1, $size - 1);
$image->setPixel(0, $i, Color::interpolate($start, $stop, pow($r, $pow)));
}
break;
}
$image->renderPNG();
break;
case 'pixel':
$color = (string)@$_GET['color'];
$color = new Color($color);
$image = new Image(1, 1);
$image->setPixel(0, 0, $color);
$image->renderPNG();
break;
}
?>
Notons qu'un argument supplémentaire, "pow", doit être fourni : il permet d'avoir une répartition non-linéaire des couleurs (dominante de l'une sur l'autre) afin d'accentuer certains effets de relief.
Sous cette forme, pour générer un dégradé blanc-noir de 100 pixels de hauteur sur un bloc #bloc par exemple, vous devrez utiliser le code CSS suivant :
#bloc {
background: #fff
url('gd.php?size=100&direction=vertical&start=000000&stop=ffffff&pow=1')
repeat-x
top;
}
Vous serez d'accord avec moi quand je dis que c'est un peu moche, voire même ignoble. Mais n'oublions pas notre cher ami l'URL Rewriting ! Si vous disposez de cette précieuse extension sur votre serveur, le fichier .htaccess suivant, à mettre dans le même répertoire, vous simplifiera la vie :
RewriteEngine on RewriteRule ^images\/v(\d+)_([0-9a-f]+)_([0-9a-f]+).png$ gd.php?type=gradient&size=$1&start=$2&stop=$3&pow=1&direction=vertical [L] RewriteRule ^images\/v(\d+)_([0-9a-f]+)_([0-9a-f]+)_(\d+.?\d*).png$ gd.php?type=gradient&size=$1&start=$2&stop=$3&pow=$4&direction=vertical [L] RewriteRule ^images\/h(\d+)_([0-9a-f]+)_([0-9a-f]+).png$ gd.php?type=gradient&size=$1&start=$2&stop=$3&pow=1&direction=horizontal [L] RewriteRule ^images\/h(\d+)_([0-9a-f]+)_([0-9a-f]+)_(\d+.?\d*).png$ gd.php?type=gradient&size=$1&start=$2&stop=$3&pow=$4&direction=horizontal [L] RewriteRule ^images\/p_([0-9a-f]+).png$ gd.php?type=pixel&color=$1 [L]
Vous pourrez dès lors utiliser une syntaxe beaucoup plus propre pour générer vos images, qui par ailleurs a l'avantage de cacher que vous utilisez un script de génération d'images :
#bloc {
background: #fff
url('images/v100_ffffff_000000.png')
repeat-x
top;
}
Comme me l'a fait remarquer KOogar de WebRankInfo, on peut se passer de l'URL Rewriting, qui n'est pas disponible sur tous les serveurs, en utilisant une syntaxe du type img.php?t=v100_ffffff_000000.png au lieu de images/v100_ffffff_000000.png.
Pour cela, il faut user des expressions rationnelles, comme dans le fichier img.php suivant :
<?php
$t = @$_GET['t'];
if (preg_match('/^v(\d+)_([0-9a-f]+)_([0-9a-f]+)$/', $t, $matches)) {
$_GET = array(
'type' => 'gradient',
'size' => $matches[1],
'start' => $matches[2],
'stop' => $matches[3],
'pow' => 1,
'direction' => 'vertical'
);
include('gd.php');
}
else if (preg_match('/^v(\d+)_([0-9a-f]+)_([0-9a-f]+)_(\d+.?\d*)$/', $t, $matches)) {
$_GET = array(
'type' => 'gradient',
'size' => $matches[1],
'start' => $matches[2],
'stop' => $matches[3],
'pow' => $matches[4],
'direction' => 'vertical'
);
include('gd.php');
}
else if (preg_match('/^h(\d+)_([0-9a-f]+)_([0-9a-f]+)$/', $t, $matches)) {
$_GET = array(
'type' => 'gradient',
'size' => $matches[1],
'start' => $matches[2],
'stop' => $matches[3],
'pow' => 1,
'direction' => 'horizontal'
);
include('gd.php');
}
else if (preg_match('/^h(\d+)_([0-9a-f]+)_([0-9a-f]+)_(\d+.?\d*)$/', $t, $matches)) {
$_GET = array(
'type' => 'gradient',
'size' => $matches[1],
'start' => $matches[2],
'stop' => $matches[3],
'pow' => $matches[4],
'direction' => 'horizontal'
);
include('gd.php');
}
else if (preg_match('/^p_([0-9a-f]+)$/', $t, $matches)) {
$_GET = array(
'type' => 'pixel',
'color' => $matches[1]
);
include('gd.php');
}
?>
Les codes couleurs fournis au script peuvent contenir une 4ème composante, elle aussi codée sur 2 caractères hexadécimaux, qui représente la transparence de la couleur (elle vaut par défaut 0). Combiné aux merveilles du format PNG, on pourra même générer des gradients de transparence !
Vous aurez remarqué qu'en plus de la fonction de génération de dégradé, j'ai intégré une très simple fonction de génération d'un simple pixel. Ceci semble inutile à première vue puisqu'on a la propriété CSS background-color, mais cela permet en spécifiant une transparence non nulle d'avoir un fond de couleur translucide ! Attention dans ce cas là à ne pas mentionner background-color en CSS, puisque sinon on aura simplement une couleur translucide écrasée sur un fond uni.
Pour aller encore plus vite, on peut n'écrire qu'une seule lettre par composante de couleur au lieu de deux : tout comme les interpréteurs CSS, le script l'interprète comme deux fois cette même lettre. Ainsi, "eee" sera équivalent à "eeeeee". On couvre déjà une bonne palette de couleurs grâce à cette syntaxe en 3 lettres (ou 4 si on inclut de la transparence).
Je n'ai pas intégré de fonction de mise en cache des images générées, ce qui fait qu'à chaque chargement d'une image, elle sera regénérée, entraînant un gâchis de puissance notable dans un environnement de production. Ce n'est pas seulement dans un souci de complexité du script que je n'ai pas intégré cette fonctionnalité, mais plutôt parce que mettre en cache systématiquement toutes les images générées remplirait inutilement votre disque dur pendant la phase de développement (où vous ferez de nombreux essais), et rendrait la tâche facile à un hacker de surcharger en puissance et en mémoire votre serveur en générant tous les dégradés possibles et imaginables en phase de production. L'idéal serait donc lors de la mise en production de télécharger toutes les images générées et de les enregistrer en dur, en n'utilisant donc ce script que pendant la phase de développement.